From patchwork Thu Oct 29 13:26:23 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ray Strode X-Patchwork-Id: 28693 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 4235AC324C for ; Thu, 29 Oct 2026 13:27:00 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id EB98C690A1; Thu, 29 Oct 2026 14:26:59 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b="aWCO0Inc"; dkim-atps=neutral Received: from mail-vs1-xe2b.google.com (mail-vs1-xe2b.google.com [IPv6:2607:f8b0:4864:20::e2b]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 90B1D68E07 for ; Thu, 29 Oct 2026 14:26:58 +0100 (CET) Received: by mail-vs1-xe2b.google.com with SMTP id ada2fe7eead31-7d06763335aso97992137.2 for ; Thu, 29 Oct 2026 06:26:58 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20260707; t=1793280417; x=1793885217; 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=zh8mCc3S85t3QNibQ+vsW6Os+PVjwfEdCMMDzh9ZqG4=; b=aWCO0Incw7jjdwHpwusHw2sMtk2aOmpUn8KtcXo5Ip6SAanqLNLpTTA3xEOusfOKLu B4mU3FXbyUDKx/NQrBrPuiEqoJoP1ECNJcsa+N8aocrbaG3C6l8gPhZSViRKZePaJCIM 2DbJ/C8//xUSrrYJqlCjKCvuUOLT/7jtoRkH9tzj9tBs3G9ojsE7A9F8c2vc1NUluggw mlUgKj68ecWOMU6Z10TYh1eyp3eMM7qpa+FkysKcEUKN1rliI2MYmOEONJQO883nWAzM AbXP+U6zObS1tOnVn97up2Zz/aB9tVFtn49TyFS5jAffvsOwIEgXimq38/bJxj9JF26x n/Eg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1793280417; x=1793885217; 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=zh8mCc3S85t3QNibQ+vsW6Os+PVjwfEdCMMDzh9ZqG4=; b=wnaQlaeUMOQOIvkad+TxyjgaGhAwqcL5zcDfduF+cyRhiOAiV4oKrQZOnfG1uXMAdC qhblxIfORFa1rbvqAyVTmyYLOWXJY8CXesLgWY4ZARoj677RXOS30RKoNVUcTTvqvIP6 dKePDv2CM+9NPLkj7CFmikfzYdUToFiotAeItH/GsNVVATEX3kPf1BJUDsvQ4g2VWMQG ohZFpPuh5UHrewS9UFIraHgWrIvqDIe3/nM59oarLhy7F5rb5W2mvm7J455KJfEtFCDf oWM26s6LIdT/4f+2GAPqoCowbWOFPgJivC0/3hVC1elwSlH/WZW1+czIvf7CNnY6qiXQ 4Smg== X-Gm-Message-State: AFq9FYJV4vA19U443WjswWSFHdq/nEn4IL6amQSLoFTqVV6QvzBn2V+r GULjZQoXsmkmDExcFPMEM9W6OD9Sa+RuIUn3p27JfxymvwTQYadlVA0P5jEoNStDA4o= X-Gm-Gg: AYBFou2Sxj0l5jDQ6s0Oft2CVO5f+xyidNWlwAaHdA4fLLeQJKbo6zdfnc8l377Tqpz uesNBnmmJaIbOEscrWXUGYghzCPYg4s/ylL4edia3/iGwLuzF4TqYd1OIiHh87WY5nvETQ//Wrl YR7pdwW73yEgTEn4JDQcMF+j+es4NCU6BNwJhav1VFF7suavbhAYgCTJeD3x/HGItHmW9gZuM0N iz8Ntb4gzQSRK9ATUKtHjcEwrRSqmIz7yc319tIcIxTFeLq1nZaG7Yzp6bymTH11hN6TGMkKozf I/oPCPRElpP6Xx0fp5Etlqlb6WsR351AQ+y9OORG3xzCfubtnCNmOiJDO/BBjPsS7D+czmhTR2N wN07dlR/RtCkgBAD9L7QwB3sMxsE9gzlMBlqohDd49VrwSoMxbiVQdE7JQpABNbDcZ6RtZUm24b l8VylGoMv0fUF5pMM8Z9ly6yiXLtYQ8vcYpJgayc8LKzCxatLUDtcX+elRevdUc8NdY7ZZ9chpB L+WAYCONjo6dlQToB7HCfAz67WCB20MvfiSSw+V+KVeZRdMvct642TWWUFYJwxQQQf+vYLgcztz 9MVbcz2JArqrzt+zNv5IGZv+PWASrHMFK5saVJGHG9h2Q62lKAh9NqBL X-Received: by 2002:a05:6102:6f09:b0:7cc:b055:f75b with SMTP id ada2fe7eead31-7d03a629bbdmr1827406137.32.1793280415891; Thu, 29 Oct 2026 06:26:55 -0700 (PDT) Received: from localhost ([144.121.52.163]) by smtp.gmail.com with ESMTPSA id a1e0cc1a2514c-99125931983sm9476046241.2.2026.10.29.06.26.49 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 29 Oct 2026 06:26:53 -0700 (PDT) From: Ray Strode To: libcamera-devel@lists.libcamera.org Cc: kieran.bingham@ideasonboard.com, bod.linux@nxsw.ie, laurent.pinchart@ideasonboard.com, stefan.klug@ideasonboard.com, martincarvalho@gmail.com Subject: [PATCH v4 1/9] libcamera: Document ScalerCrop in physical pixel-array coordinates Date: Thu, 29 Oct 2026 09:26:23 -0400 Message-ID: <20261029132631.370507-2-halfline@gmail.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261029132631.370507-1-halfline@gmail.com> References: <20261016202818.1405645-1-halfline@gmail.com> <20261029132631.370507-1-halfline@gmail.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" From: Ray Strode The ScalerCrop documentation describes its origin relative to an active area, while pipelines report physical pixel-array coordinates. The ScalerCropMaximum description also refers to the active pixel array. Describe both rectangles relative to PixelArrayArea, with coordinates and dimensions in native sensor pixels. Reported-by: Stefan Klug Signed-off-by: Ray Strode --- src/libcamera/control_ids_core.yaml | 7 +++---- src/libcamera/property_ids_core.yaml | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) base-commit: b174b52cb8700320b20cd3a5cb275107f908e0c1 diff --git a/src/libcamera/control_ids_core.yaml b/src/libcamera/control_ids_core.yaml index 73643e7e..5c33db5f 100644 --- a/src/libcamera/control_ids_core.yaml +++ b/src/libcamera/control_ids_core.yaml @@ -668,64 +668,63 @@ controls: method of combination is platform dependent. In this respect, it is not necessarily aimed at providing a way to implement a focus algorithm by the application, rather an indication of how in-focus a frame is. - ColourCorrectionMatrix: type: float direction: inout description: | The 3x3 matrix that converts camera RGB to sRGB within the imaging pipeline. This should describe the matrix that is used after pixels have been white-balanced, but before any gamma transformation. The 3x3 matrix is stored in conventional reading order in an array of 9 floating point values. ColourCorrectionMatrix can only be applied in a Request when the AWB is disabled. \sa AwbEnable \sa ColourTemperature size: [3,3] - ScalerCrop: type: Rectangle direction: inout description: | Sets the image portion that will be scaled to form the whole of the final output image. - The (x,y) location of this rectangle is relative to the - PixelArrayActiveAreas that is being used. The units remain native - sensor pixels, even if the sensor is being used in a binning or - skipping mode. + The rectangle is expressed in native sensor pixels relative to the + properties::PixelArrayArea rectangle, even if the sensor is being used + in a binning or skipping mode. This control is only present when the pipeline supports scaling. Its maximum valid value is given by the properties::ScalerCropMaximum property, and the two can be used to implement digital zoom. - DigitalGain: type: float direction: inout description: | Digital gain value applied during the processing steps applied to the image as captured from the sensor. The global digital gain factor is applied to all the colour channels of the RAW image. Different pipeline models are free to specify how the global gain factor applies to each separate channel. If an imaging pipeline applies digital gain in distinct processing steps, this value indicates their total sum. Pipelines are free to decide how to adjust each processing step to respect the received gain factor and shall report their total value in the request metadata. - FrameDuration: type: int64_t direction: out description: | The instantaneous frame duration from start of frame exposure to start of next exposure, expressed in microseconds. diff --git a/src/libcamera/property_ids_core.yaml b/src/libcamera/property_ids_core.yaml index 171af2a3..f3b4a4aa 100644 --- a/src/libcamera/property_ids_core.yaml +++ b/src/libcamera/property_ids_core.yaml @@ -560,62 +560,62 @@ controls: | |//////| | | y2 o+---+------+---+| | ||///|//////|///|| | PixelArrayArea.height y3 o+---+------+---+| | | |//////| | | y4 o +------+ | | +----+------+----+ / The property reports two rectangles PixelArrayActiveAreas = ((x2, y1, x3 - x2 + 1, y4 - y1 + 1), (x1, y2, x4 - x1 + 1, y3 - y2 + 1)) The first rectangle describes the maximum field-of-view of all image formats in the 4:3 resolutions, while the second one describes the maximum field of view for all image formats in the 16:9 resolutions. Multiple rectangles shall only be reported when the sensor can't capture the pixels in the corner regions. If all the pixels in the (x1,y1) - (x4,y4) area can be captured, the PixelArrayActiveAreas property shall contains the single rectangle (x1,y1) - (x4,y4). \todo Rename this property to ActiveAreas once we will have property categories (i.e. Properties::PixelArray::ActiveAreas) - ScalerCropMaximum: type: Rectangle description: | The maximum valid rectangle for the controls::ScalerCrop control. This reflects the minimum mandatory cropping applied in the camera sensor and - the rest of the pipeline. Just as the ScalerCrop control, it defines a - rectangle taken from the sensor's active pixel array. + the rest of the pipeline. The rectangle is expressed in native sensor + pixels relative to the PixelArrayArea rectangle. This property is valid only after the camera has been successfully configured and its value may change whenever a new configuration is applied. \todo Turn this property into a "maximum control value" for the ScalerCrop control once "dynamic" controls have been implemented. - SensorSensitivity: type: float description: | The relative sensitivity of the chosen sensor mode. Some sensors have readout modes with different sensitivities. For example, a binned camera mode might, with the same exposure and gains, produce twice the signal level of the full resolution readout. This would be signalled by the binned mode, when it is chosen, indicating a value here that is twice that of the full resolution mode. This value will be valid after the configure method has returned successfully. - SystemDevices: type: int64_t size: [n] description: | A list of integer values of type dev_t denoting the major and minor device numbers of the underlying devices used in the operation of this camera. Different cameras may report identical devices. From patchwork Thu Oct 29 13:26:24 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ray Strode X-Patchwork-Id: 28694 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 292DDC3261 for ; Thu, 29 Oct 2026 13:27:04 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id D5657690AA; Thu, 29 Oct 2026 14:27:03 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b="wxvAE0kI"; dkim-atps=neutral Received: from mail-vs1-xe2d.google.com (mail-vs1-xe2d.google.com [IPv6:2607:f8b0:4864:20::e2d]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 0CD126909B for ; Thu, 29 Oct 2026 14:27:01 +0100 (CET) Received: by mail-vs1-xe2d.google.com with SMTP id ada2fe7eead31-7cc2bb73cd9so611007137.3 for ; Thu, 29 Oct 2026 06:27:00 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20260707; t=1793280420; x=1793885220; 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=eiwkemerKEObigwDJiP0dfbVwgplTC6iMDJv0APNU3c=; b=wxvAE0kI8LFPE0MLlJzlWK1Z+sKJdBk4Yi7VsxAcm5/UAJbvA9IVmaeDd/rpV3xH4U 0YMKI60o65gsN4wGxDwaILicMJ1DO4jQ0trLl8JCIrwxULneYlGdJrb3s5B6A8NEbvZt l/d04B7FfMn3EQIpxvgavWafs7F4bBit7spDyrp/FaYXUUBPeZsHdTCzVhtvVOvCAW9k KNYjFJhxcfZFpZltxis4kOfA9caHffR0ozEFslGrF3ZLt9HqrRObKCk4/hgCikdOW2rs M3F2iEuniyZKYMshJpkiPrcM7bukUGi2pBOHweteH8ls8WcIMvuYPLYSdhtXmRT7NUsD Ersw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1793280420; x=1793885220; 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=eiwkemerKEObigwDJiP0dfbVwgplTC6iMDJv0APNU3c=; b=VvyIZ3avs9dc9J99nlqC/YRtaEZG382LJRrDxM/84lEild6mGhwmoCTzr0V3I+Tjb/ GytC1IiOK1Z/QmSZHlSDf4flSsbivsFW3npnx8X6h6RoZ+L1c6ufVWyfXImlqbuATb8j PSTG8BuSPkDSonbYCvFE2SGNPszB4c/OmhTPxzcBVairDA/OrS5Ah4HstKehhPbyoTt9 gLmlLQdZKdx7ZmufqU5VHOiTyqYO3dX+P+q0nuxiJS3HzlzPJ2VHdblb48Gkd10GMuHO +fTkBhQBrAW3e6S4sbEngHspGjtGNYMH2YoIUjrjMyt6pgWUkEVQUgqNBneB1jkWyT3H hjgg== X-Gm-Message-State: AFq9FYKge/q45BGlm9DPDM254msm6RESEIgdBgkJnc5B0vKYzXHl+CHT DOV4eM7pcpdj7zMMLA5O+VT2MdZeBXUOBrCbiJYQNJ5YDWScYIGvxzYWNWm4vvKdmRc= X-Gm-Gg: AYBFou1MeFUw/fVGo3Gd/k8gtRa09zzQul1CQAXE/Z5Ogg+Hlppg0G/6QwYRSmmpZx1 9XfLcxRGhY/SASzvmPYHELfmqvsSmTK1rpV628ImtPDemplka8s5wIROqR/asVEQuzwP3UCTfjd E1X5Rr03kes3z2YO5igQQHqKMhTJ90EWrl2zb57ntl+1KhCW7+30/rierPaXcSuK4U62Aosf4XX WEK+gf/RCSxnsxZ1dAyNC3pwPw3jhAwgi58CVT2ZOwDo/YkzA5DHpJ5ehvs9WoU4QzgQi/xDXQK IleIvYeKEyJOzxSKp/I6Dopnl/OuBWnCFRjJnFgo/7NIFSgb4zEvoXFlwOZMtw82tzNchqRUnWw ohHlOptGIegSGv/ytlth3IePw3PI89sWfVOr4sk3DnHSe2CvUU2F7+vTKw00ZNYxD5tEiUsI8yq CJ7KEgCeJhVlD7cXWx7DbxnFIM/JPR16VZfp5xYQjsOnCSEQgEYjiUeVD2JOnu7cPbmoSR+k3AA mauiQmg3Q/94Vo7W21g0Xja/IgFBah3YzMuCemECiGoG/fJR6J9d211pVcDFtPuvtTjKY09NCwd DzL0FLy168fT27r6VRzEEa1xXcLlvlRf/8w88OCF4QNCTboA1uyEbY6C X-Received: by 2002:a05:6102:1499:b0:7b2:508e:3d57 with SMTP id ada2fe7eead31-7d03a289d03mr2358760137.4.1793280419679; Thu, 29 Oct 2026 06:26:59 -0700 (PDT) Received: from localhost ([144.121.52.163]) by smtp.gmail.com with ESMTPSA id a1e0cc1a2514c-99125931983sm9476046241.2.2026.10.29.06.26.56 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 29 Oct 2026 06:26:58 -0700 (PDT) From: Ray Strode To: libcamera-devel@lists.libcamera.org Cc: kieran.bingham@ideasonboard.com, bod.linux@nxsw.ie, laurent.pinchart@ideasonboard.com, stefan.klug@ideasonboard.com, martincarvalho@gmail.com Subject: [PATCH v4 2/9] software-isp: Align statistics with the visible sensor crop Date: Thu, 29 Oct 2026 09:26:24 -0400 Message-ID: <20261029132631.370507-3-halfline@gmail.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261029132631.370507-1-halfline@gmail.com> References: <20261016202818.1405645-1-halfline@gmail.com> <20261029132631.370507-1-halfline@gmail.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" From: Ray Strode This commit changes statistics gathering to follow the sensor area visible in the GPU output. The GPU debayer path fills the requested output with a centered crop of the sensor image. CPU statistics read the original sensor buffer, but their window uses the output dimensions and starts at the buffer's origin. A centered origin is calculated but never passed to the statistics engine. Metering therefore covers a different sensor region when the image is cropped or scaled. The window is now derived in sensor coordinates, accounting for Bayer border removal and cropping to the output aspect ratio, and centered in the sensor buffer. Changing resolution without changing aspect ratio leaves the rectangle unchanged. Signed-off-by: Ray Strode --- .../internal/software_isp/debayer_geometry.h | 23 +++++++++++++++++++ .../internal/software_isp/meson.build | 1 + src/libcamera/software_isp/debayer_egl.cpp | 14 ++++------- src/libcamera/software_isp/debayer_egl.h | 1 - 4 files changed, 28 insertions(+), 11 deletions(-) create mode 100644 include/libcamera/internal/software_isp/debayer_geometry.h diff --git a/include/libcamera/internal/software_isp/debayer_geometry.h b/include/libcamera/internal/software_isp/debayer_geometry.h new file mode 100644 index 00000000..8812a3cf --- /dev/null +++ b/include/libcamera/internal/software_isp/debayer_geometry.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026, Red Hat Inc. + * + * Geometry for scaling and cropping debayered images + */ + +#pragma once + + +#include + +namespace libcamera { + +/* Return the visible sensor rectangle, before statistics Bayer alignment. */ +inline Rectangle debayerCrop(const Size &inputSize, const Size &nativeSize, + const Size &outputSize) +{ + const Size visibleSize = nativeSize.boundedToAspectRatio(outputSize); + return visibleSize.centeredTo(Rectangle(inputSize).center()); +} + +} /* namespace libcamera */ diff --git a/include/libcamera/internal/software_isp/meson.build b/include/libcamera/internal/software_isp/meson.build index df7c3b97..e18597ed 100644 --- a/include/libcamera/internal/software_isp/meson.build +++ b/include/libcamera/internal/software_isp/meson.build @@ -1,9 +1,10 @@ # SPDX-License-Identifier: CC0-1.0 libcamera_internal_headers += files([ 'benchmark.h', + 'debayer_geometry.h', 'debayer_params.h', 'software_isp.h', 'swisp_stats.h', 'swstats_cpu.h', ]) diff --git a/src/libcamera/software_isp/debayer_egl.cpp b/src/libcamera/software_isp/debayer_egl.cpp index 3356070c..5a27a38d 100644 --- a/src/libcamera/software_isp/debayer_egl.cpp +++ b/src/libcamera/software_isp/debayer_egl.cpp @@ -1,56 +1,57 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * Copyright (C) 2024, Linaro Ltd. * * Authors: * Bryan O'Donoghue * */ #include "debayer_egl.h" #include #include #include #include #include #include #include #include #include #include #include "libcamera/internal/formats.h" #include "libcamera/internal/framebuffer.h" +#include "libcamera/internal/software_isp/debayer_geometry.h" #include "../shaders/glsl_shaders.h" namespace libcamera { /** * \class DebayerEGL * \brief Class for debayering using an EGL Shader * * Implements an EGL shader based debayering solution. */ /** * \brief Construct a DebayerEGL object * \param[in] stats Statistics processing object * \param[in] cm The camera manager * \param[in] display The EGL display to use */ DebayerEGL::DebayerEGL(std::unique_ptr stats, const CameraManager &cm, EGLDisplay display) : Debayer(cm), stats_(std::move(stats)), egl_(display) { } DebayerEGL::~DebayerEGL() { } int DebayerEGL::getInputConfig(PixelFormat inputFormat, DebayerInputConfig &config) { BayerFormat bayerFormat = @@ -304,72 +305,65 @@ int DebayerEGL::configure(const StreamConfiguration &inputCfg, inputConfig_.stride = inputCfg.stride; inputPixelFormat_ = inputCfg.pixelFormat; width_ = inputCfg.size.width; height_ = inputCfg.size.height; if (outputCfgs.size() != 1) { LOG(Debayer, Error) << "Unsupported number of output streams: " << outputCfgs.size(); return -EINVAL; } const StreamConfiguration &outputCfg = outputCfgs[0]; SizeRange outSizeRange = sizes(inputCfg.pixelFormat, inputCfg.size); std::tie(outputConfig_.stride, outputConfig_.frameSize) = strideAndFrameSize(outputCfg.pixelFormat, outputCfg.size); if (!outSizeRange.contains(outputCfg.size) || outputConfig_.stride != outputCfg.stride) { LOG(Debayer, Error) << "Invalid output size/stride: " << "\n " << outputCfg.size << " (" << outSizeRange << ")" << "\n " << outputCfg.stride << " (" << outputConfig_.stride << ")"; return -EINVAL; } outputPixelFormat_ = outputCfg.pixelFormat; outputSize_ = outputCfg.size; nativeOutputSize_ = outSizeRange.max; - window_.x = ((inputCfg.size.width - outputCfg.size.width) / 2) & - ~(inputConfig_.patternSize.width - 1); - window_.y = ((inputCfg.size.height - outputCfg.size.height) / 2) & - ~(inputConfig_.patternSize.height - 1); - window_.width = outputCfg.size.width; - window_.height = outputCfg.size.height; - /* - * Don't pass x,y from window_ since process() already adjusts for it. - * But crop the window to 2/3 of its width and height for speedup. + * The GPU scales the visible sensor crop to outputSize_. Statistics + * consume the original input buffer, so use sensor coordinates here. */ - stats_->setWindow(Rectangle(window_.size())); + stats_->setWindow(debayerCrop(inputCfg.size, nativeOutputSize_, outputSize_)); inputBufferCount_ = inputCfg.bufferCount; outputBufferCount_ = outputCfg.bufferCount; return 0; } Size DebayerEGL::patternSize(PixelFormat inputFormat) { DebayerEGL::DebayerInputConfig config; if (getInputConfig(inputFormat, config) != 0) return {}; return config.patternSize; } std::vector DebayerEGL::formats(PixelFormat inputFormat) { DebayerEGL::DebayerInputConfig config; if (getInputConfig(inputFormat, config) != 0) return std::vector(); return config.outputFormats; } std::tuple DebayerEGL::strideAndFrameSize(const PixelFormat &outputFormat, const Size &size) { diff --git a/src/libcamera/software_isp/debayer_egl.h b/src/libcamera/software_isp/debayer_egl.h index cbbc6a45..6910fa49 100644 --- a/src/libcamera/software_isp/debayer_egl.h +++ b/src/libcamera/software_isp/debayer_egl.h @@ -93,41 +93,40 @@ private: float firstRed_y_; GLint attributeVertex_; GLint attributeTexture_; GLint textureUniformStep_; GLint textureUniformSize_; GLint textureUniformStrideFactor_; GLint textureUniformBayerFirstRed_; GLint textureUniformProjMatrix_; GLint textureUniformBayerDataIn_; /* Per-frame AWB gains */ GLint awbUniformDataIn_; /* Lens shading correction */ GLint textureUniformLsc_; /* Represent per-frame CCM as a uniform vector of floats 3 x 3 */ GLint ccmUniformDataIn_; /* Black Level compensation */ GLint blackLevelUniformDataIn_; /* Gamma */ GLint gammaUniformDataIn_; /* Contrast */ GLint contrastExpUniformDataIn_; Size nativeOutputSize_; - Rectangle window_; std::unique_ptr stats_; eGL egl_; uint32_t width_; uint32_t height_; GLint glFormat_; unsigned int bytesPerPixel_; uint32_t shaderStridePixels_; }; } /* namespace libcamera */ From patchwork Thu Oct 29 13:26:25 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ray Strode X-Patchwork-Id: 28695 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 C53F0C3261 for ; Thu, 29 Oct 2026 13:27:07 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 6EB88690BC; Thu, 29 Oct 2026 14:27:07 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b="jOI0RTsc"; dkim-atps=neutral Received: from mail-ua1-x934.google.com (mail-ua1-x934.google.com [IPv6:2607:f8b0:4864:20::934]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 2A2D2690BB for ; Thu, 29 Oct 2026 14:27:05 +0100 (CET) Received: by mail-ua1-x934.google.com with SMTP id a1e0cc1a2514c-9908e3cd939so629226241.3 for ; Thu, 29 Oct 2026 06:27:05 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20260707; t=1793280424; x=1793885224; 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=qSr2bYahiOdbnIb4c5UFDJoaWJRzP1IKK/iHNfBbWok=; b=jOI0RTscYhABRviVqfrRS1CoBx9IC2O7esYrSbH/xTk76cPzYBLXSRbPhjcMqDNMef sbHnQ7nvZOAPmTyCMBf0DkJDvkDv/BhuwiO6nD1wzAH36OWAYt53HbdGz4KVdVVdHZ7t vNtQb4iZYMfEP45LKxnsSCwsF+yi3r7mpAl8RtetAAKgTMHvhMVNOBDOI298Pkj5axZl 8uiSRh2txwva2RdSKGa3L+nkMep2Vr7+PcAAR5rHfK0vsEgG4gwxNMOXjkNL/LcK55lA GLXf1Ldj72WjYMcr/ucFWhSdlf2MuwS3wm65Y6qlIajnz3XoTAejNCPUcsQkQJpID+CC qlqA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1793280424; x=1793885224; 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=qSr2bYahiOdbnIb4c5UFDJoaWJRzP1IKK/iHNfBbWok=; b=b+i9yVGqw6ed046ItYFExQIYoMW6SQeJvkGm6FU07qpATO+awB2ei/bnbeM28pY/zz x9048agIB17Vgx+uvi+xfWC3LxgXIFh1N/etX7go4GBu7A4N2n9W1f94MWfjjdI5p2U9 9jJnnoiTQ7Z6xiw+8fTGHBXZJlJxNJzmn/m2oHwjjtSZUQPBSvhDiih3uLCxMJb3805w bdb7qC0ULNQhWlz3OBhEZDLt1BYWf1I7RjrU8YCOCkNz1UxoTKcb6MrSZRHj1l/VryCn 4mmcR9vmCnYxAs7pFi79ncTaaldvIZFq1fRgKkNL4DBRi8Yaeyny9PZTisrzrXRcPzy4 bQdw== X-Gm-Message-State: AFq9FYJ0F9geQeqUsyWqR03i8k1ymDptTxfGrwpRY8XiKqrDhlPtei9m DH/cvY/CcUV+oabN0a1kjf+i/23YwMqToc+GAfiMvFS5BDzGT4OZu6cUgfGyfGpbPYU= X-Gm-Gg: AYBFou2qLv/ZN/78FwKaNFEcLuJysOqQpMk1A4f/TyJdw2pc4ueXZXbaeX52W6BkzWx qDBbH2hE1I1Lu2MH2DsSha+nIzu1PTTWKJE+toLIV3rrQeJSFwljgY24d5Oaxzte6yk66GQ+4vR /RoSk5nZzZ1pxjWX9/nlxTyxhBnhrwM25+6noEllFEmLDIM+/EbYGxRU75k4tb7vq+s4rGwWD4F YOlC8k1Ss5YaVYXRfole1ZnEhQNssdhu5tJ8zwLF2T8yOthMc/i7NBe0O3Dh6olFZStcwYKaMs8 y3JLPGFEyoApcIgYO+b+UFePM9mwELAG9koeUKdpr8X/PCcxoxElH6aVwmGu5mIqlfo93cTFBpc TlJzDNahUYR5nXDTV5bLQEXp1zypT0JF+NgqWKlWYYqWa+JLd6Q1eFM17yNqvXIiYDxWhdwYnSs 7zcmV1+jwITbywfryZY3Rg72u6A3o2wsMkZD/ohO8wDtE89iiRt9Q+zF3NoXODTmgSUXeSGNJ1d 7CmsNRjwsnIa4FqRZ1a7KyJZluvFHqYuU11v1GSUGHKq+NTrvdH/R97CK1n6/cHjNXloX9hionl 9D5QhKtUkzhGWok2lvHIO/Wx3Ngk4pe29nOMBZHRMx09DGB1F3Qqeusv X-Received: by 2002:a05:6102:3309:b0:7c3:a6f0:99cb with SMTP id ada2fe7eead31-7d03a5fece5mr2310985137.33.1793280423699; Thu, 29 Oct 2026 06:27:03 -0700 (PDT) Received: from localhost ([144.121.52.163]) by smtp.gmail.com with ESMTPSA id a1e0cc1a2514c-99125931983sm9476046241.2.2026.10.29.06.27.00 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 29 Oct 2026 06:27:02 -0700 (PDT) From: Ray Strode To: libcamera-devel@lists.libcamera.org Cc: kieran.bingham@ideasonboard.com, bod.linux@nxsw.ie, laurent.pinchart@ideasonboard.com, stefan.klug@ideasonboard.com, martincarvalho@gmail.com Subject: [PATCH v4 3/9] tests: Validate the visible sensor crop Date: Thu, 29 Oct 2026 09:26:25 -0400 Message-ID: <20261029132631.370507-4-halfline@gmail.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261029132631.370507-1-halfline@gmail.com> References: <20261016202818.1405645-1-halfline@gmail.com> <20261029132631.370507-1-halfline@gmail.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" From: Ray Strode This commit adds a geometry unit test with expected sensor rectangles for native, downscaled, wider, narrower, and portrait outputs. It also checks integer rounding that leaves an odd top coordinate before Bayer alignment. Signed-off-by: Ray Strode --- test/debayer-geometry.cpp | 63 +++++++++++++++++++++++++++++++++++++++ test/meson.build | 1 + 2 files changed, 64 insertions(+) create mode 100644 test/debayer-geometry.cpp diff --git a/test/debayer-geometry.cpp b/test/debayer-geometry.cpp new file mode 100644 index 00000000..cfc757fa --- /dev/null +++ b/test/debayer-geometry.cpp @@ -0,0 +1,63 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2026, Red Hat Inc. + * + * Sensor crop tests + */ + +#include + +#include + +#include "libcamera/internal/software_isp/debayer_geometry.h" + +#include "test.h" + +using namespace libcamera; + +class DebayerGeometryTest : public Test +{ +protected: + int run() override + { + if (testCrop()) + return TestFail; + + return TestPass; + } + +private: + int testCrop() + { + static const struct { + Size input; + Size native; + Size output; + Rectangle expected; + } cases[] = { + { { 1920, 1080 }, { 1920, 1080 }, { 1920, 1080 }, { 0, 0, 1920, 1080 } }, + { { 1928, 1092 }, { 1920, 1092 }, { 1920, 1092 }, { 4, 0, 1920, 1092 } }, + /* Output resolution changes must not shrink the sensor window. */ + { { 1928, 1092 }, { 1920, 1092 }, { 1920, 1080 }, { 4, 6, 1920, 1080 } }, + { { 1928, 1092 }, { 1920, 1092 }, { 1280, 720 }, { 4, 6, 1920, 1080 } }, + { { 1928, 1092 }, { 1920, 1092 }, { 640, 480 }, { 236, 0, 1456, 1092 } }, + { { 1928, 1092 }, { 1920, 1092 }, { 512, 768 }, { 600, 0, 728, 1092 } }, + { { 1928, 1092 }, { 1920, 1092 }, { 1280, 640 }, { 4, 66, 1920, 960 } }, + /* A smaller Bayer border leaves an odd top coordinate. */ + { { 1928, 1092 }, { 1924, 1092 }, { 1280, 720 }, { 2, 5, 1924, 1082 } }, + }; + + for (const auto &entry : cases) { + Rectangle crop = debayerCrop(entry.input, entry.native, entry.output); + if (crop != entry.expected) { + std::cerr << "Crop " << entry.input << " -> " << entry.output + << " is " << crop << ", expected " << entry.expected << std::endl; + return TestFail; + } + } + + return TestPass; + } +}; + +TEST_REGISTER(DebayerGeometryTest) diff --git a/test/meson.build b/test/meson.build index 363a2829..5027b7b1 100644 --- a/test/meson.build +++ b/test/meson.build @@ -25,60 +25,61 @@ endif subdir('libtest') subdir('camera') subdir('controls') subdir('gstreamer') subdir('ipa') subdir('ipc') subdir('log') subdir('media_device') subdir('process') subdir('py') subdir('serialization') subdir('stream') subdir('v4l2_compat') subdir('v4l2_subdevice') subdir('v4l2_videodevice') public_tests = [ {'name': 'color-space', 'sources': ['color-space.cpp']}, {'name': 'geometry', 'sources': ['geometry.cpp']}, {'name': 'public-api', 'sources': ['public-api.cpp']}, {'name': 'signal', 'sources': ['signal.cpp']}, {'name': 'transform', 'sources': ['transform.cpp']}, ] internal_tests = [ {'name': 'bayer-format', 'sources': ['bayer-format.cpp']}, {'name': 'byte-stream-buffer', 'sources': ['byte-stream-buffer.cpp']}, {'name': 'camera-sensor', 'sources': ['camera-sensor.cpp']}, + {'name': 'debayer-geometry', 'sources': ['debayer-geometry.cpp']}, {'name': 'delayed_controls', 'sources': ['delayed_controls.cpp']}, {'name': 'event', 'sources': ['event.cpp']}, {'name': 'event-dispatcher', 'sources': ['event-dispatcher.cpp']}, {'name': 'event-thread', 'sources': ['event-thread.cpp']}, {'name': 'file', 'sources': ['file.cpp']}, {'name': 'flags', 'sources': ['flags.cpp']}, {'name': 'hotplug-cameras', 'sources': ['hotplug-cameras.cpp']}, {'name': 'matrix', 'sources': ['matrix.cpp']}, {'name': 'message', 'sources': ['message.cpp']}, {'name': 'object', 'sources': ['object.cpp']}, {'name': 'object-delete', 'sources': ['object-delete.cpp']}, {'name': 'object-invoke', 'sources': ['object-invoke.cpp']}, {'name': 'pixel-format', 'sources': ['pixel-format.cpp']}, {'name': 'shared-fd', 'sources': ['shared-fd.cpp']}, {'name': 'signal-threads', 'sources': ['signal-threads.cpp']}, {'name': 'threads', 'sources': 'threads.cpp', 'dependencies': [libthreads]}, {'name': 'timer', 'sources': ['timer.cpp']}, {'name': 'timer-fail', 'sources': ['timer-fail.cpp'], 'should_fail': true}, {'name': 'timer-thread', 'sources': ['timer-thread.cpp']}, {'name': 'unique-fd', 'sources': ['unique-fd.cpp']}, {'name': 'utils', 'sources': ['utils.cpp']}, {'name': 'value-node', 'sources': ['value-node.cpp']}, {'name': 'vector', 'sources': ['vector.cpp']}, {'name': 'yaml-parser', 'sources': ['yaml-parser.cpp']}, ] internal_non_parallel_tests = [ {'name': 'fence', 'sources': ['fence.cpp']}, {'name': 'mapped-buffer', 'sources': ['mapped-buffer.cpp']}, ] From patchwork Thu Oct 29 13:26:26 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ray Strode X-Patchwork-Id: 28696 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 BB713C3261 for ; Thu, 29 Oct 2026 13:27:11 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 71C05690BF; Thu, 29 Oct 2026 14:27:11 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b="I4cDCnCF"; dkim-atps=neutral Received: from mail-vs1-xe2c.google.com (mail-vs1-xe2c.google.com [IPv6:2607:f8b0:4864:20::e2c]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id BB43B68E07 for ; Thu, 29 Oct 2026 14:27:09 +0100 (CET) Received: by mail-vs1-xe2c.google.com with SMTP id ada2fe7eead31-78fb1fb9508so602339137.1 for ; Thu, 29 Oct 2026 06:27:09 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20260707; t=1793280428; x=1793885228; 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=QyZ8thwMCGw70fPVVwaP3ssbbEYD7uBXQJer8IqBj/I=; b=I4cDCnCF5bNRsHjJef+tGIuPFXx5aU88ZY5E0OBL1MXNbTofT6Mx+dFMiKIQ3lDzG9 J0oImVs45TjDB/zD4Xx7ecCoedhuQgHNX33ARtJex8Ukym/2aXy0zTj4RzbpjHiqdMri mU2+u1IqkxWiZreWUhlxsA5yr14i0S0Gcv5nA0ruKz3DHcWp30LoyT8/3I7T7OGtSM9Q mdLSp5dGf0dR2QgeyXDMgimlK7T/6faExKJ+/l7y2JD7edKKC8jV9/JLRb7wTa4xkZVD yAwcWWMqR5ta2Ovy4ewOTpYOz5W1NApwcuKvdJyqWoiwN6bPo5fseHOXhLgQUpwe+1qQ Fn/w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1793280428; x=1793885228; 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=QyZ8thwMCGw70fPVVwaP3ssbbEYD7uBXQJer8IqBj/I=; b=LCgf8qLlWaAX2p9xsNjcsAqKOQ74Lj/kGcET8f7ye3JqlPmefocTwEbPut84WDQ4GB 73zY3lkHyonH0R1rAFe3XPIlvn2UHdMCCLXM7ZkghOCHTMjP072a1tODppFtjnHueq+6 BBIlCiKHmjSSyzCjPC5vy3gwGWT+nfwYPxRbxj38w+SuVcAX5OInpw8+xLYG/uJul09n dGc9zj/o9TmEEFLudiPkjVL4acqKcxS3qR5HuG6tDG6iIsjNv5il7keHAu78NP4onDU/ paHciP0/rhRsS/0fEAknQTtBGY3yUq8dOHi1YwydTIzbe2f4UuDyjSyZrKddPG0JlNaE dHTg== X-Gm-Message-State: AFq9FYJ/VX/v5d8yK0WddmFgmrOm43YWEaj864oGScQ0urkmNyXPT9mQ U9D/eCo0jiZqYUOrmqlrammAm6Dgs2h/UpCiis7lTXD+cpIaL15E26oMFhnMCio1d9I= X-Gm-Gg: AYBFou3f99evv/5joHF4+aYeMh1R+sM76uSpjUX3n3oXXCH8TeBOdVu6mNGHyYsBH+d 9mEkLXOk0MFL6rOleCeMI87iOZ9gs/fWPjhsIVty1TH1czbITj1SmJxfFWN36WKEKFyUZnEp8hu yWNa6gVK7KiJ4aJ6Tikm3sledcgae8+XWzNtrZCJDj7u89agCJtJnqpROqAVgG4Eoj3LZyvUV+Z o6YKibJGv+EkkBLwDT9b1mhhIumOM6iDI6Znxawvd5UuIkzFD9sLCAV1nvDfiLAbzuShm9Mrf74 NihFEv6+peI3lU68ox/shlOPEsMKcm43lUIMnHH0THFX1xJamt8HUOZk037l2kjuhitZbJrFJk6 tbkRMJHdDkYUIFl2jx/y/vsORWKrki5+S9gpL2+W37hYPEkuBediBo5wieLb7ikO8yYOZjA+utS 4gDgc3nw2LmEqGNpo2QMuZ4d4UVAOxEZ4mBT1OAaG7tD8Z4AAVlhMCbq2Igln66m1UMweF9pmde O/TCg2tB2eAzwiCFWM1tjuZLvamITnNm6v6jRNrTTVR1SOpxQoBROvVuTcaI5bi/fMtVnFqmNjw ThTLqnY8lNBg8cMCkdivG3BswHweDjB4tH+0zXYtN8vbqx80B2f02GsGWA== X-Received: by 2002:a67:e112:0:b0:7b0:bd98:68df with SMTP id ada2fe7eead31-7d0543acf6dmr633810137.41.1793280428358; Thu, 29 Oct 2026 06:27:08 -0700 (PDT) Received: from localhost ([144.121.52.163]) by smtp.gmail.com with ESMTPSA id a1e0cc1a2514c-99125931983sm9476046241.2.2026.10.29.06.27.04 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 29 Oct 2026 06:27:07 -0700 (PDT) From: Ray Strode To: libcamera-devel@lists.libcamera.org Cc: kieran.bingham@ideasonboard.com, bod.linux@nxsw.ie, laurent.pinchart@ideasonboard.com, stefan.klug@ideasonboard.com, martincarvalho@gmail.com Subject: [PATCH v4 4/9] software-isp: Expose the configured image crop Date: Thu, 29 Oct 2026 09:26:26 -0400 Message-ID: <20261029132631.370507-5-halfline@gmail.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261029132631.370507-1-halfline@gmail.com> References: <20261016202818.1405645-1-halfline@gmail.com> <20261029132631.370507-1-halfline@gmail.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" From: Ray Strode This commit makes the image region selected by the debayer available to the pipeline handler, so ScalerCrop metadata can describe the processed output. The CPU and EGL backends crop input differently. CPU debayering keeps an output-sized window whose origin depends on the Bayer pattern and output channel order, while EGL scales a centered crop to fill the output. Each backend returns its actual configured image crop in input-buffer coordinates. The EGL crop is retained before statistics alignment, so the returned rectangle describes the image rather than its metering samples. Signed-off-by: Ray Strode --- .../libcamera/internal/software_isp/software_isp.h | 1 + src/libcamera/software_isp/debayer.cpp | 11 +++++++++++ src/libcamera/software_isp/debayer.h | 1 + src/libcamera/software_isp/debayer_cpu.h | 4 ++++ src/libcamera/software_isp/debayer_egl.cpp | 3 ++- src/libcamera/software_isp/debayer_egl.h | 2 ++ src/libcamera/software_isp/software_isp.cpp | 14 ++++++++++++++ 7 files changed, 35 insertions(+), 1 deletion(-) diff --git a/include/libcamera/internal/software_isp/software_isp.h b/include/libcamera/internal/software_isp/software_isp.h index b6491063..d32bb7d2 100644 --- a/include/libcamera/internal/software_isp/software_isp.h +++ b/include/libcamera/internal/software_isp/software_isp.h @@ -40,60 +40,61 @@ class Debayer; class FrameBuffer; class PixelFormat; class Stream; struct StreamConfiguration; LOG_DECLARE_CATEGORY(SoftwareIsp) class SoftwareIsp : public Object { public: SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor, ControlInfoMap *ipaControls); ~SoftwareIsp(); int loadConfiguration([[maybe_unused]] const std::string &filename) { return 0; } bool isValid() const; std::vector formats(PixelFormat input); SizeRange sizes(PixelFormat inputFormat, const Size &inputSize); std::tuple strideAndFrameSize(const PixelFormat &outputFormat, const Size &size); uint32_t preferredInputStride(const PixelFormat &inputFormat, const Size &size); int configure(const StreamConfiguration &inputCfg, const std::vector> &outputCfgs, const ipa::softisp::IPAConfigInfo &configInfo, ControlInfoMap *ipaControls); + Rectangle inputCrop() const; int exportBuffers(const Stream *stream, unsigned int count, std::vector> *buffers); void processStats(const uint32_t frame, const uint32_t bufferId, const ControlList &sensorControls); int start(); void stop(); void queueRequest(const uint32_t frame, const ControlList &controls); int queueBuffers(uint32_t frame, FrameBuffer *input, const std::map &outputs); void process(uint32_t frame, FrameBuffer *input, FrameBuffer *output); Signal inputBufferReady; Signal outputBufferReady; Signal ispStatsReady; Signal metadataReady; Signal setSensorControls; private: void saveIspParams(uint32_t frame); void setSensorCtrls(const ControlList &sensorControls); void statsReady(uint32_t frame, uint32_t bufferId); void inputReady(FrameBuffer *input); void outputReady(FrameBuffer *output); std::unique_ptr debayer_; Thread ispWorkerThread_; diff --git a/src/libcamera/software_isp/debayer.cpp b/src/libcamera/software_isp/debayer.cpp index 4f665900..c7b1b2b3 100644 --- a/src/libcamera/software_isp/debayer.cpp +++ b/src/libcamera/software_isp/debayer.cpp @@ -79,60 +79,71 @@ namespace libcamera { * not apply any real lens shading correction. */ /** * \var DebayerParams::lscLutVersion * \brief Incremented on each \a lscLut change */ /** * \class Debayer * \brief Base debayering class * * Base class that provides functions for setting up the debayering process. */ LOG_DEFINE_CATEGORY(Debayer) /** * \brief Construct a Debayer object * \param[in] cm The camera manager */ Debayer::Debayer(const CameraManager &cm) : bench_(cm, "Debayer") { } Debayer::~Debayer() { } +/** + * \fn Rectangle Debayer::inputCrop() const + * \brief Retrieve the input region visible in the configured output + * + * The rectangle is in input-buffer coordinates and excludes any additional + * alignment performed for statistics sampling. It is valid after a successful + * configure() call. + * + * \return The configured image crop + */ + /** * \fn int Debayer::configure() * \brief Configure the debayer object according to the passed in parameters * \param[in] inputCfg The input configuration * \param[in] outputCfgs The output configurations * \param[in] ccmEnabled Whether a color correction matrix is applied * \param[in] lscEnabled Whether lens shading correction grid is provided * * \return 0 on success, a negative errno on failure */ /** * \fn Size Debayer::patternSize(PixelFormat inputFormat) * \brief Get the width and height at which the bayer pattern repeats * \param[in] inputFormat The input format * * Valid sizes are: 2x2, 4x2 or 4x4. * * \return Pattern size or an empty size for unsupported inputFormats */ /** * \fn std::vector Debayer::formats(PixelFormat inputFormat) * \brief Get the supported output formats * \param[in] inputFormat The input format * * \return All supported output formats or an empty vector if there are none */ /** diff --git a/src/libcamera/software_isp/debayer.h b/src/libcamera/software_isp/debayer.h index e30a0527..ef36f3c8 100644 --- a/src/libcamera/software_isp/debayer.h +++ b/src/libcamera/software_isp/debayer.h @@ -27,60 +27,61 @@ namespace libcamera { class CameraManager; class FrameBuffer; LOG_DECLARE_CATEGORY(Debayer) class Debayer : public Object { public: Debayer(const CameraManager &cm); virtual ~Debayer() = 0; virtual int configure(const StreamConfiguration &inputCfg, const std::vector> &outputCfgs, bool ccmEnabled, bool lscEnabled) = 0; virtual std::vector formats(PixelFormat inputFormat) = 0; virtual std::tuple strideAndFrameSize(const PixelFormat &outputFormat, const Size &size) = 0; virtual uint32_t preferredInputStride([[maybe_unused]] const PixelFormat &inputFormat, [[maybe_unused]] const Size &size) { return 0; } virtual void process(uint32_t frame, FrameBuffer *input, FrameBuffer *output, const DebayerParams ¶ms) = 0; virtual int start() { return 0; } virtual void stop() {} virtual SizeRange sizes(PixelFormat inputFormat, const Size &inputSize) = 0; + virtual Rectangle inputCrop() const = 0; virtual const SharedFD &getStatsFD() = 0; unsigned int frameSize() { return outputConfig_.frameSize; } Signal inputBufferReady; Signal outputBufferReady; struct DebayerInputConfig { Size patternSize; unsigned int bpp; unsigned int stride; std::vector outputFormats; }; struct DebayerOutputConfig { unsigned int stride; unsigned int frameSize; }; DebayerInputConfig inputConfig_; DebayerOutputConfig outputConfig_; Size outputSize_; PixelFormat inputPixelFormat_; PixelFormat outputPixelFormat_; bool swapRedBlueGains_; Benchmark bench_; private: virtual Size patternSize(PixelFormat inputFormat) = 0; diff --git a/src/libcamera/software_isp/debayer_cpu.h b/src/libcamera/software_isp/debayer_cpu.h index ae570893..0c025b75 100644 --- a/src/libcamera/software_isp/debayer_cpu.h +++ b/src/libcamera/software_isp/debayer_cpu.h @@ -20,60 +20,64 @@ #include #include "libcamera/internal/bayer_format.h" #include "libcamera/internal/software_isp/debayer_params.h" #include "libcamera/internal/software_isp/swstats_cpu.h" #include "debayer.h" namespace libcamera { class DebayerCpuThread; class DebayerCpu : public Debayer { public: DebayerCpu(std::unique_ptr stats, const CameraManager &cm); ~DebayerCpu(); int configure(const StreamConfiguration &inputCfg, const std::vector> &outputCfgs, bool ccmEnabled, bool lscEnabled) override; Size patternSize(PixelFormat inputFormat) override; std::vector formats(PixelFormat input) override; std::tuple strideAndFrameSize(const PixelFormat &outputFormat, const Size &size) override; void process(uint32_t frame, FrameBuffer *input, FrameBuffer *output, const DebayerParams ¶ms) override; int start() override; void stop() override; SizeRange sizes(PixelFormat inputFormat, const Size &inputSize) override; + Rectangle inputCrop() const override + { + return window_.translatedBy({ static_cast(xShift_), 0 }); + } const SharedFD &getStatsFD() override { return stats_->getStatsFD(); } private: friend class DebayerCpuThread; /** * \brief Called to debayer 1 line of Bayer input data to output format * \param[out] dst Pointer to the start of the output line to write * \param[in] src The input data * * Input data is an array of (patternSize_.height + 1) src * pointers each pointing to a line in the Bayer source. The middle * element of the array will point to the actual line being processed. * Earlier element(s) will point to the previous line(s) and later * element(s) to the next line(s). * * These functions take an array of src pointers, rather than * a single src pointer + a stride for the source, so that when the src * is slow uncached memory it can be copied to faster memory before * debayering. Debayering a standard 2x2 Bayer pattern requires access * to the previous and next src lines for interpolating the missing * colors. To allow copying the src lines only once 3 temporary buffers * each holding a single line are used, re-using the oldest buffer for * the next line and the pointers are swizzled so that: * src[0] = previous-line, src[1] = currrent-line, src[2] = next-line. * This way the 3 pointers passed to the debayer functions form * a sliding window over the src avoiding the need to copy each * line more than once. * * Similarly for bayer patterns which repeat every 4 lines, 5 src diff --git a/src/libcamera/software_isp/debayer_egl.cpp b/src/libcamera/software_isp/debayer_egl.cpp index 5a27a38d..d6636302 100644 --- a/src/libcamera/software_isp/debayer_egl.cpp +++ b/src/libcamera/software_isp/debayer_egl.cpp @@ -309,61 +309,62 @@ int DebayerEGL::configure(const StreamConfiguration &inputCfg, height_ = inputCfg.size.height; if (outputCfgs.size() != 1) { LOG(Debayer, Error) << "Unsupported number of output streams: " << outputCfgs.size(); return -EINVAL; } const StreamConfiguration &outputCfg = outputCfgs[0]; SizeRange outSizeRange = sizes(inputCfg.pixelFormat, inputCfg.size); std::tie(outputConfig_.stride, outputConfig_.frameSize) = strideAndFrameSize(outputCfg.pixelFormat, outputCfg.size); if (!outSizeRange.contains(outputCfg.size) || outputConfig_.stride != outputCfg.stride) { LOG(Debayer, Error) << "Invalid output size/stride: " << "\n " << outputCfg.size << " (" << outSizeRange << ")" << "\n " << outputCfg.stride << " (" << outputConfig_.stride << ")"; return -EINVAL; } outputPixelFormat_ = outputCfg.pixelFormat; outputSize_ = outputCfg.size; nativeOutputSize_ = outSizeRange.max; /* * The GPU scales the visible sensor crop to outputSize_. Statistics * consume the original input buffer, so use sensor coordinates here. */ - stats_->setWindow(debayerCrop(inputCfg.size, nativeOutputSize_, outputSize_)); + inputCrop_ = debayerCrop(inputCfg.size, nativeOutputSize_, outputSize_); + stats_->setWindow(inputCrop_); inputBufferCount_ = inputCfg.bufferCount; outputBufferCount_ = outputCfg.bufferCount; return 0; } Size DebayerEGL::patternSize(PixelFormat inputFormat) { DebayerEGL::DebayerInputConfig config; if (getInputConfig(inputFormat, config) != 0) return {}; return config.patternSize; } std::vector DebayerEGL::formats(PixelFormat inputFormat) { DebayerEGL::DebayerInputConfig config; if (getInputConfig(inputFormat, config) != 0) return std::vector(); return config.outputFormats; } std::tuple DebayerEGL::strideAndFrameSize(const PixelFormat &outputFormat, const Size &size) { diff --git a/src/libcamera/software_isp/debayer_egl.h b/src/libcamera/software_isp/debayer_egl.h index 6910fa49..90e361ce 100644 --- a/src/libcamera/software_isp/debayer_egl.h +++ b/src/libcamera/software_isp/debayer_egl.h @@ -34,99 +34,101 @@ namespace libcamera { #define DEBAYER_EGL_MIN_SIMPLE_RGB_GAIN_TEXTURE_UNITS 4 #define DEBAYER_OPENGL_COORDS 4 class CameraManager; class DebayerEGL : public Debayer { public: DebayerEGL(std::unique_ptr stats, const CameraManager &cm, EGLDisplay display); ~DebayerEGL(); int configure(const StreamConfiguration &inputCfg, const std::vector> &outputCfgs, bool ccmEnabled, bool lscEnabled) override; Size patternSize(PixelFormat inputFormat) override; std::vector formats(PixelFormat input) override; std::tuple strideAndFrameSize(const PixelFormat &outputFormat, const Size &size) override; uint32_t preferredInputStride(const PixelFormat &inputFormat, const Size &size) override; void process(uint32_t frame, FrameBuffer *input, FrameBuffer *output, const DebayerParams ¶ms) override; int start() override; void stop() override; const SharedFD &getStatsFD() override { return stats_->getStatsFD(); } SizeRange sizes(PixelFormat inputFormat, const Size &inputSize) override; + Rectangle inputCrop() const override { return inputCrop_; } private: static int getInputConfig(PixelFormat inputFormat, DebayerInputConfig &config); int initBayerShaders(PixelFormat inputFormat, PixelFormat outputFormat); int getShaderVariableLocations(); void setShaderVariableValues(eGLImage &eGLImageIn, const DebayerParams ¶ms); int debayerGPU(FrameBuffer *input, FrameBuffer *output, const DebayerParams ¶ms, std::optional *mappedInputBuffer, std::optional *inputBufferDmaSyncer); eGLImage *getCachedInputFrameBuffer(FrameBuffer *input, std::optional *inMapped, std::optional *inDmaSyncer); eGLImage *getCachedOutputFrameBuffer(FrameBuffer *output); /* Shader program identifiers */ GLuint vertexShaderId_ = 0; GLuint fragmentShaderId_ = 0; GLuint programId_ = 0; /* Pointer to object representing input texture */ std::deque>> eglImageInCache_; std::deque>> eglImageOutCache_; unsigned int inputBufferCount_; unsigned int outputBufferCount_; /* LSC lookup table */ bool lscEnabled_; std::optional eglImageLscLookup_; std::optional lscLutVersion_; /* Shader parameters */ float firstRed_x_; float firstRed_y_; GLint attributeVertex_; GLint attributeTexture_; GLint textureUniformStep_; GLint textureUniformSize_; GLint textureUniformStrideFactor_; GLint textureUniformBayerFirstRed_; GLint textureUniformProjMatrix_; GLint textureUniformBayerDataIn_; /* Per-frame AWB gains */ GLint awbUniformDataIn_; /* Lens shading correction */ GLint textureUniformLsc_; /* Represent per-frame CCM as a uniform vector of floats 3 x 3 */ GLint ccmUniformDataIn_; /* Black Level compensation */ GLint blackLevelUniformDataIn_; /* Gamma */ GLint gammaUniformDataIn_; /* Contrast */ GLint contrastExpUniformDataIn_; Size nativeOutputSize_; + Rectangle inputCrop_; std::unique_ptr stats_; eGL egl_; uint32_t width_; uint32_t height_; GLint glFormat_; unsigned int bytesPerPixel_; uint32_t shaderStridePixels_; }; } /* namespace libcamera */ diff --git a/src/libcamera/software_isp/software_isp.cpp b/src/libcamera/software_isp/software_isp.cpp index 627625b4..8a06e53d 100644 --- a/src/libcamera/software_isp/software_isp.cpp +++ b/src/libcamera/software_isp/software_isp.cpp @@ -279,60 +279,74 @@ uint32_t SoftwareIsp::preferredInputStride(const PixelFormat &inputFormat, const * \brief Configure the SoftwareIsp object according to the passed in parameters * \param[in] inputCfg The input configuration * \param[in] outputCfgs The output configurations * \param[in] configInfo The IPA configuration data, received from the pipeline handler * \param[out] ipaControls The IPA controls to update * \return 0 on success, a negative errno on failure */ int SoftwareIsp::configure(const StreamConfiguration &inputCfg, const std::vector> &outputCfgs, const ipa::softisp::IPAConfigInfo &configInfo, ControlInfoMap *ipaControls) { ASSERT(ipa_ && debayer_); int ret = ipa_->configure(configInfo, ipaControls); if (ret < 0) return ret; ret = debayer_->configure(inputCfg, outputCfgs, ccmEnabled_, lscEnabled_); if (ret < 0) return ret; LOG(SoftwareIsp, Info) << "Input " << inputCfg.size << "-" << BayerFormat::fromPixelFormat(inputCfg.pixelFormat) << " stride " << inputCfg.stride; return 0; } +/** + * \brief Retrieve the input region used to produce the configured output + * + * The rectangle is expressed in input-buffer pixels, before any Bayer + * alignment applied by the statistics engine. It is valid after configure() + * succeeds and describes the selected CPU or EGL debayer backend. + * + * \return The configured input crop + */ +Rectangle SoftwareIsp::inputCrop() const +{ + return debayer_->inputCrop(); +} + /** * \brief Export the buffers from the Software ISP * \param[in] stream Output stream exporting the buffers * \param[in] count Number of buffers to allocate * \param[out] buffers Vector to store the allocated buffers * \return The number of allocated buffers on success or a negative error code * otherwise */ int SoftwareIsp::exportBuffers(const Stream *stream, unsigned int count, std::vector> *buffers) { ASSERT(debayer_ != nullptr); /* single output for now */ if (stream == nullptr) return -EINVAL; return dmaHeap_.exportBuffers(count, { debayer_->frameSize() }, buffers); } /** * \brief Queue a request and process the control list from the application * \param[in] frame The number of the frame which will be processed next * \param[in] controls The controls for the \a frame */ void SoftwareIsp::queueRequest(const uint32_t frame, const ControlList &controls) { ipa_->queueRequest(frame, controls); } From patchwork Thu Oct 29 13:26:27 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ray Strode X-Patchwork-Id: 28697 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 D12A4C3261 for ; Thu, 29 Oct 2026 13:27:15 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 80555690C6; Thu, 29 Oct 2026 14:27:15 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b="rMftM209"; dkim-atps=neutral Received: from mail-vs1-xe2d.google.com (mail-vs1-xe2d.google.com [IPv6:2607:f8b0:4864:20::e2d]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 483FF68E07 for ; Thu, 29 Oct 2026 14:27:13 +0100 (CET) Received: by mail-vs1-xe2d.google.com with SMTP id ada2fe7eead31-7cb6dc850c7so683517137.0 for ; Thu, 29 Oct 2026 06:27:13 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20260707; t=1793280432; x=1793885232; 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=15OpypEOMfzejTaMux0ThDcUk4K2DawSq0n9ubUEwWY=; b=rMftM209/kkx/zq/9SXISDiIXLhizPaw+zoJPEA9mVHBeDCCTEPXcT6sfb7g5AgPI8 d4s3B9oV6ZbDc6r2I1fR8wCPP0y6U3q0GEbqWtWx5k9aOYUNZhdSJQaDHgNNR0IWnyBD nUSI+tlxQc7ifu52z/+BSJvwfAfmYTCtP9cf4Gp1e3fjt3rZOec/H4c+klNW3yMOArlV KsX2kmRdHWqjyWXeM1fc9WTZm58Cqk1SKmXG40BxjKRxoq9iBNSwofVnFCGrI32qrsgq 2cGjHJDyBHc9iK3mmIoHaFYwfWHE/RJbLxXPAo1mBY2UFC5OG0ruQOAEEnTOVG47jKRo +VUQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1793280432; x=1793885232; 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=15OpypEOMfzejTaMux0ThDcUk4K2DawSq0n9ubUEwWY=; b=P+UsTxUah5BS3ddlGE6yxLnY6bA5e8mKRDOnQMR3dvNp+hJSLiCee2tbSPia4whIMj qF3dtMTnt767Ab0WY9+W0aKpGoEv/TfsG9lmSXK4D7nwA/jCqFeRf8Qz7o9inlCbu6gC YELkUbmYwWJpEk5QDJLfFDrnn00e1Rdwf0BB7AViuv6YH4tjWB6JRpVHzaGF/6TohlBE eM0rDgu0Ikmn1eu6fAkSp87/le3INU1CgPgq2vfnTTK4p1ooq8T1cY2dV3IFBG5jec6C UUc3bqM8CPiMjIQTgdzinLMt+Bdlp12b54ZiDHNUMiF5A/apZaexVo580JnNqrOR0q6G Y70A== X-Gm-Message-State: AFq9FYKZjIcM5OEd6d+pCZDl7CGjTHs6rZ0mgcBZzXveo0nsN77PIt+x 0h2FVbu20fhY2cS2rlSNvcYlOUio56Jjx+jYzaXXbiqVwSfVHIJwIv21DsDCCVx3c5o= X-Gm-Gg: AYBFou0nONF8cR383vGQBuADg9QFu3/BK0XTBiQ0X+AeaPScXHxc01fUtdVXG4Y8Xr6 1lXkAO76MFvLYKTbTD8yjX7g+QM9EzdthYAAS4l6Wut1J9WzH6+/jZJQC+UHLi29UtZaZdEprq2 tDcfIpXjz7DNRgYFgqE8iqG+C5uNdrupv4skR08gcfjI41TEP7SXeiNVaT1HWODawVHMYXyIe2D QHnVDCAOOJq6S2g6g/NSSwAX0X5mba5tghq12mG/MbwnrjkJzKeJClDBs7joXsap1sFNK1W4ar0 6Q2MknfkzsKxnNatAMVddXN/XeOE3i8sKAIvw9nzakFcFyIXX0ru/gePjn1bnLPA+8GpvSv1yAq VZcjqTt+naoVJg1HmVM1/rMDMPn2outKgotlnarAmI67wNwETcv7QFWmi9H7FuMR8jWx76bBXUl PHCQzZ3a4bwyfi50t82rwpNWyNLLtn5RTRaEhOLSULcz9OCTlH9zh3VYS9RXN8ZkqH1sig0aPTQ yLb0dhUHyH+KD55SFfTFsz2ykSdMEAZJf/W+LFm0TS2UuH5FBEU4n+/ylVrFPS9kalMPBqN8vwC qzetouiE/XkdMWm18+IJDb4ftMQOpkbuDWHsYV0UmUFySLtp4ar7kC0gOQ== X-Received: by 2002:a05:6102:418d:b0:7cc:1a41:20e7 with SMTP id ada2fe7eead31-7d03a504abdmr2055263137.7.1793280432027; Thu, 29 Oct 2026 06:27:12 -0700 (PDT) Received: from localhost ([144.121.52.163]) by smtp.gmail.com with ESMTPSA id a1e0cc1a2514c-99125931983sm9476046241.2.2026.10.29.06.27.08 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 29 Oct 2026 06:27:11 -0700 (PDT) From: Ray Strode To: libcamera-devel@lists.libcamera.org Cc: kieran.bingham@ideasonboard.com, bod.linux@nxsw.ie, laurent.pinchart@ideasonboard.com, stefan.klug@ideasonboard.com, martincarvalho@gmail.com Subject: [PATCH v4 5/9] tests: Check the configured CPU image crop Date: Thu, 29 Oct 2026 09:26:27 -0400 Message-ID: <20261029132631.370507-6-halfline@gmail.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261029132631.370507-1-halfline@gmail.com> References: <20261016202818.1405645-1-halfline@gmail.com> <20261029132631.370507-1-halfline@gmail.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" From: Ray Strode This commit checks the crop returned by CPU debayering for supported Bayer input formats and both RGB channel orders. Unpacked kernels may shift the first rendered pixel by one column depending on the Bayer pattern and output channel order. The reported image region must include that shift, while the specialized packed kernels retain their unshifted origin. The configuration-only cases cover all four standard Bayer orders at 8, 10 and 12 bits, including packed RAW10 and RAW12. Reusing the debayer across configurations also checks that changing formats clears a previous phase shift. Signed-off-by: Ray Strode --- test/meson.build | 1 + test/software_isp/debayer-crop.cpp | 102 +++++++++++++++++++++++++++++ test/software_isp/meson.build | 20 ++++++ 3 files changed, 123 insertions(+) create mode 100644 test/software_isp/debayer-crop.cpp create mode 100644 test/software_isp/meson.build diff --git a/test/meson.build b/test/meson.build index 5027b7b1..fc72d105 100644 --- a/test/meson.build +++ b/test/meson.build @@ -8,60 +8,61 @@ endif test_enabled = true # When ASan is enabled, find the path to the ASan runtime needed by multiple # tests. This currently works with gcc only, as clang uses different file names # depending on the compiler version and target architecture. asan_enabled = false asan_runtime_missing = false if get_option('b_sanitize').contains('address') asan_enabled = true if cc.get_id() == 'gcc' asan_runtime = run_command(cc, '-print-file-name=libasan.so', check : true).stdout().strip() else asan_runtime_missing = true endif endif subdir('libtest') subdir('camera') subdir('controls') subdir('gstreamer') subdir('ipa') subdir('ipc') subdir('log') subdir('media_device') subdir('process') subdir('py') subdir('serialization') +subdir('software_isp') subdir('stream') subdir('v4l2_compat') subdir('v4l2_subdevice') subdir('v4l2_videodevice') public_tests = [ {'name': 'color-space', 'sources': ['color-space.cpp']}, {'name': 'geometry', 'sources': ['geometry.cpp']}, {'name': 'public-api', 'sources': ['public-api.cpp']}, {'name': 'signal', 'sources': ['signal.cpp']}, {'name': 'transform', 'sources': ['transform.cpp']}, ] internal_tests = [ {'name': 'bayer-format', 'sources': ['bayer-format.cpp']}, {'name': 'byte-stream-buffer', 'sources': ['byte-stream-buffer.cpp']}, {'name': 'camera-sensor', 'sources': ['camera-sensor.cpp']}, {'name': 'debayer-geometry', 'sources': ['debayer-geometry.cpp']}, {'name': 'delayed_controls', 'sources': ['delayed_controls.cpp']}, {'name': 'event', 'sources': ['event.cpp']}, {'name': 'event-dispatcher', 'sources': ['event-dispatcher.cpp']}, {'name': 'event-thread', 'sources': ['event-thread.cpp']}, {'name': 'file', 'sources': ['file.cpp']}, {'name': 'flags', 'sources': ['flags.cpp']}, {'name': 'hotplug-cameras', 'sources': ['hotplug-cameras.cpp']}, {'name': 'matrix', 'sources': ['matrix.cpp']}, {'name': 'message', 'sources': ['message.cpp']}, {'name': 'object', 'sources': ['object.cpp']}, {'name': 'object-delete', 'sources': ['object-delete.cpp']}, {'name': 'object-invoke', 'sources': ['object-invoke.cpp']}, diff --git a/test/software_isp/debayer-crop.cpp b/test/software_isp/debayer-crop.cpp new file mode 100644 index 00000000..4f9f8b9a --- /dev/null +++ b/test/software_isp/debayer-crop.cpp @@ -0,0 +1,102 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2026, Red Hat Inc. + * + * CPU debayer crop configuration tests + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "libcamera/internal/formats.h" +#include "libcamera/internal/software_isp/swstats_cpu.h" + +#include "debayer_cpu.h" +#include "test.h" + +using namespace libcamera; + +class DebayerCropTest : public Test +{ +protected: + int run() override + { + CameraManager manager; + auto stats = std::make_unique(manager); + if (!stats->isValid()) + return TestFail; + + DebayerCpu debayer(std::move(stats), manager); + static const struct { + PixelFormat input; + int rgbShift; + int bgrShift; + } cases[] = { + { formats::SBGGR8, 0, 1 }, + { formats::SGBRG8, 1, 0 }, + { formats::SGRBG8, 0, 1 }, + { formats::SRGGB8, 1, 0 }, + { formats::SBGGR10, 0, 1 }, + { formats::SGBRG10, 1, 0 }, + { formats::SGRBG10, 0, 1 }, + { formats::SRGGB10, 1, 0 }, + { formats::SBGGR12, 0, 1 }, + { formats::SGBRG12, 1, 0 }, + { formats::SGRBG12, 0, 1 }, + { formats::SRGGB12, 1, 0 }, + /* Packed kernels use each Bayer order without a phase shift. */ + { formats::SBGGR10_CSI2P, 0, 0 }, + { formats::SGBRG10_CSI2P, 0, 0 }, + { formats::SGRBG10_CSI2P, 0, 0 }, + { formats::SRGGB10_CSI2P, 0, 0 }, + { formats::SBGGR12_CSI2P, 0, 0 }, + { formats::SGBRG12_CSI2P, 0, 0 }, + { formats::SGRBG12_CSI2P, 0, 0 }, + { formats::SRGGB12_CSI2P, 0, 0 }, + }; + + for (const auto &entry : cases) { + StreamConfiguration input; + input.pixelFormat = entry.input; + input.size = { 1928, 1092 }; + input.stride = PixelFormatInfo::info(entry.input).stride(input.size.width, 0); + + for (PixelFormat format : { formats::RGB888, formats::BGR888, + formats::XRGB8888, formats::XBGR8888 }) { + StreamConfiguration output; + output.pixelFormat = format; + output.size = { 1280, 720 }; + std::tie(output.stride, output.frameSize) = + debayer.strideAndFrameSize(format, output.size); + const std::vector> outputs{ output }; + if (debayer.configure(input, outputs, false, false)) { + std::cerr << "Unable to configure " << entry.input << " -> " << format + << std::endl; + return TestFail; + } + + int shift = (format == formats::RGB888 || format == formats::XRGB8888) + ? entry.rgbShift + : entry.bgrShift; + Rectangle expected(324 + shift, 186, 1280, 720); + if (debayer.inputCrop() != expected) { + std::cerr << "Crop for " << entry.input << " -> " << format << " is " + << debayer.inputCrop() << ", expected " << expected << std::endl; + return TestFail; + } + } + } + + return TestPass; + } +}; + +TEST_REGISTER(DebayerCropTest) diff --git a/test/software_isp/meson.build b/test/software_isp/meson.build new file mode 100644 index 00000000..ebabde59 --- /dev/null +++ b/test/software_isp/meson.build @@ -0,0 +1,20 @@ +# SPDX-License-Identifier: CC0-1.0 + +if not softisp_enabled + subdir_done() +endif + +software_isp_tests = [ + {'name': 'debayer-crop', 'sources': ['debayer-crop.cpp']}, +] + +foreach test : software_isp_tests + exe = executable(test['name'], test['sources'], + dependencies : libcamera_private, + implicit_include_directories : false, + link_with : test_libraries, + include_directories : [test_includes_internal, + '../../src/libcamera/software_isp/']) + + test(test['name'], exe, suite : 'software_isp') +endforeach From patchwork Thu Oct 29 13:26:28 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ray Strode X-Patchwork-Id: 28698 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 CE46EC3261 for ; Thu, 29 Oct 2026 13:27:19 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 7FAA8690CB; Thu, 29 Oct 2026 14:27:19 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b="msGHMP5C"; dkim-atps=neutral Received: from mail-vs1-xe2e.google.com (mail-vs1-xe2e.google.com [IPv6:2607:f8b0:4864:20::e2e]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 2856C690AD for ; Thu, 29 Oct 2026 14:27:17 +0100 (CET) Received: by mail-vs1-xe2e.google.com with SMTP id ada2fe7eead31-7cc2bb73cd9so611148137.3 for ; Thu, 29 Oct 2026 06:27:17 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20260707; t=1793280436; x=1793885236; 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=wqVCNhP15UBhBTL2gpMDBx/KxYpGklVAZwaJOyunk3s=; b=msGHMP5CAVckOmqfDHu6LkVLlBi46SWU8KCw2erLKpjNR5Ntwtt0fLx9HdeIsTcwVy v1NC+pBMEAvk1MR8NH0R4QkEE/TMYZsLt3kshVZuzpiuBMAUNctLCH1b3sHEWyHy6WPc wUfsrJvQPAXMYnyWW9Ga3Tu4bwljivgnq/TLJQHZL1BfyggnhcyZ+/gHLUBDPDUP+0MT +eAamZdU1fL4STwCSQIF1+u6ayc/jGM529A/v3cbPjt9nd0gDmiY/JmqVBTuw2pyTC0x Y/kjIQNN1hsrAaEKdpzso1ACEZPhMd4ZanvU0ZNzomn3ih3Y5xIk3Nipxg5ez781Rdwt i+OA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1793280436; x=1793885236; 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=wqVCNhP15UBhBTL2gpMDBx/KxYpGklVAZwaJOyunk3s=; b=NfiiyzdDGo6EGhBBdDUEzanlngka72UBCAEd9lzN5ULygHukhnZTDDupg0ygH8L9cS ZjlCl/usonNLlV7f0QsnLw6wFAushTP72shpaR2FkLTzdGW2xXIEJwFB9hzo/696vBB5 P8EaVN9Krv7bjKhWVMtiefuAJrycnWJjafhgOJNYAY5PSsUay7reAYo19jAeDsxvXrev B7lyLSfhqy7v4Mcu3uObAyqK07UTV148KWo0kUSdTP1VO8zh7o6D5yZdUfw61mgs1yQS ta7d6qgMDZzGklqroledPSL3v38H8M7VTPySVTi0dlMOy9nzwIDL8ujlHRmNaMWEcxNW xXvg== X-Gm-Message-State: AFq9FYLx9dqNUAExZxynyostnKxZq+3vpdFnPdkwlCTqZWsGCvHCnwYl KUHzZdJqdfepljNuRHNMdZXhYcLR7xIeC9IVvU3VYqbtqhde3U2EE5S1v8J4MsbK0V0= X-Gm-Gg: AYBFou1r66AO4+DSlRMmaKatZYVB1DCT4GD41iK3gXKvervycbYXywWZj76sA7lYFUR E3jeCjNa9BsATCymppHBIWJiQGhcwOUcApzgON8LudB5m9+li9pHGmA16zk/ahMOXe10hVniUR9 DWdgUmZ7F+xkHB16Fccg+88jADp0yb32nnacer9zFNOT4AtplXFssmka7xCobnW3eQCerSLuslA 5+TVuKdNsAy9IHIid8Gz+z01zr8sFXvbnBwy3ZriuUMgkoa3RAJ8khKyIOca8LZBibT30kBK30c NvHGPkEhXlrpjYFhlSxDTEv39esDHXdRiIF41HWLgFpqMQrFVeABLSxsd+QNzt/f9m2nika5Nu1 DMEDRdPgFje4B1AyqGx4OwvGxzLs3jH55XtQ7eksW9+PJZg2x7ovXpG9KsOa18pAWYICM20Saly JAXSysRc7RqXl833I2BAmYPK9w5DGh+Np3M4OKDd8fQv2Z+TXCyTx9RaJMMK3X0tzRtquMNjyfX cfCbBGFJEbwvv9AZO3Vj1c5iH0whvxKjg1z4eCLZudCdEg9L1kah6ypOhY2o1IWassUFHT7Svci RaWIN/yZQVmDpbGX+eGDH3xRQiSFciUoiU8lNPeJ7TXcp5THC0hXj7vGnhDgq4z8sxlfkYP2ZkY L3g== X-Received: by 2002:a05:6102:1499:b0:7b2:508e:3d57 with SMTP id ada2fe7eead31-7d03a289d03mr2359042137.4.1793280435831; Thu, 29 Oct 2026 06:27:15 -0700 (PDT) Received: from localhost ([144.121.52.163]) by smtp.gmail.com with ESMTPSA id a1e0cc1a2514c-99125931983sm9476046241.2.2026.10.29.06.27.12 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 29 Oct 2026 06:27:15 -0700 (PDT) From: Ray Strode To: libcamera-devel@lists.libcamera.org Cc: kieran.bingham@ideasonboard.com, bod.linux@nxsw.ie, laurent.pinchart@ideasonboard.com, stefan.klug@ideasonboard.com, martincarvalho@gmail.com Subject: [PATCH v4 6/9] software-isp: Map input crops to native sensor coordinates Date: Thu, 29 Oct 2026 09:26:28 -0400 Message-ID: <20261029132631.370507-7-halfline@gmail.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261029132631.370507-1-halfline@gmail.com> References: <20261016202818.1405645-1-halfline@gmail.com> <20261029132631.370507-1-halfline@gmail.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" From: Ray Strode This commit adds the coordinate conversion needed to report a debayer crop as ScalerCrop metadata. Debayer crops use captured-buffer pixels. ScalerCrop uses physical pixel array coordinates in native sensor pixels, so sensor binning, analog crop offsets and sensor transforms must be accounted for. The conversion reverses the sensor transform and maps the input rectangle into the analog crop. Integer coordinates and dimensions follow the truncation used by the existing rectangle transformation. Signed-off-by: Ray Strode --- .../internal/software_isp/debayer_geometry.h | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/include/libcamera/internal/software_isp/debayer_geometry.h b/include/libcamera/internal/software_isp/debayer_geometry.h index 8812a3cf..bf6c6771 100644 --- a/include/libcamera/internal/software_isp/debayer_geometry.h +++ b/include/libcamera/internal/software_isp/debayer_geometry.h @@ -1,23 +1,48 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * Copyright (C) 2026, Red Hat Inc. * * Geometry for scaling and cropping debayered images */ #pragma once +#include #include +#include namespace libcamera { /* Return the visible sensor rectangle, before statistics Bayer alignment. */ inline Rectangle debayerCrop(const Size &inputSize, const Size &nativeSize, const Size &outputSize) { const Size visibleSize = nativeSize.boundedToAspectRatio(outputSize); return visibleSize.centeredTo(Rectangle(inputSize).center()); } +/* + * Map an image crop to physical pixel-array coordinates in native sensor pixels. + * The input must cover the whole analog crop. Undo the sensor transform + * before accounting for binning and the analog crop offset. Integer + * coordinates and dimensions are truncated as in transformedBetween(). + */ +inline Rectangle debayerCropToSensor(Rectangle crop, Size inputSize, + const Rectangle &analogCrop, + Transform transform) +{ + if (!!(transform & Transform::Transpose)) { + std::swap(crop.x, crop.y); + std::swap(crop.width, crop.height); + std::swap(inputSize.width, inputSize.height); + } + if (!!(transform & Transform::HFlip)) + crop.x = inputSize.width - crop.x - crop.width; + if (!!(transform & Transform::VFlip)) + crop.y = inputSize.height - crop.y - crop.height; + + return crop.transformedBetween(Rectangle(inputSize), analogCrop); +} + } /* namespace libcamera */ From patchwork Thu Oct 29 13:26:29 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ray Strode X-Patchwork-Id: 28699 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 C84A6C3261 for ; Thu, 29 Oct 2026 13:27:24 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 80FD0690CE; Thu, 29 Oct 2026 14:27:24 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b="dkzkXH6N"; dkim-atps=neutral Received: from mail-vs1-xe36.google.com (mail-vs1-xe36.google.com [IPv6:2607:f8b0:4864:20::e36]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id E80E4690AD for ; Thu, 29 Oct 2026 14:27:22 +0100 (CET) Received: by mail-vs1-xe36.google.com with SMTP id ada2fe7eead31-7cc365db987so1240070137.0 for ; Thu, 29 Oct 2026 06:27:22 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20260707; t=1793280442; x=1793885242; 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=k02R0dOKvGKKUWEKpipLqZBuCumZlT8j3xKek7NANnw=; b=dkzkXH6N7R+sDUnftxyHelIvhv0IvYhPLyzFF3v2zMEiVDZcFeU0RPX4Pg9DqKQy8J XqphhhtIgUg8fVZENz/pvqfwxcOKUCcHst7lTsrVIExZZKs0Vn9DPEKUJcaafiDHPv7v HqXDWy6I8H+pyFrcSIxZ/N6dqYJm0aZBKJxQ5nPCua87FxpXsBC3Bpx1fKPfmOmyh66l O/e758qFnvokQ9aFHjT56ngv2cqSaGsq+minN5eWQ9gcVvWIF5K6kZnjgB86p8wQmFpJ gvdenaiWryJey+LbuaGBtfUsqULgE6SiHswx1R0udrsr1rBWra4e/ZqvoLV0nNV3dSWr snGw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1793280442; x=1793885242; 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=k02R0dOKvGKKUWEKpipLqZBuCumZlT8j3xKek7NANnw=; b=Wgw6+Tx3EufLjkuUsnSlZuGL4Y/wD35NghSKaSOykwD+spnSf7SjzmJVuA3cGb9wa5 vvs//CsgAznycTGknE7hPSW2Q5waCXQlNxFt4dK2EqwdHOIWkPkzfngtjTTieVM1adHx UEdUCuNCUt/ltTfuTCgOkLGUrW+dMMI3Zdq0rhTvl0sU/rjXuA5IxFeevYhZjXJgrmNA 5JgzJQ8ICJk2/CzBkrdN+lz2kHJxUgCVcUPBjdZPB64xXWDO9Qxf9TtHUIP9IG12QMlK VT0+YNSs/UN8mv+XsMZJnf1qaJZJHKj/XmpOuXfO3z/lIQXoVWw2RUpTKmpR5K9GDfNP kU6g== X-Gm-Message-State: AFq9FYJFzq1QiSSE2j/B2wE7vXgEZTjMJrFLM/NQjYNJ2ElIFjP63lVR T09NXCdmmaH9C46PhpYT0+mKpHu5i0/5f8vH97slEU3ySq5rvWlI0+HqonaKcOaGBlo= X-Gm-Gg: AYBFou1dVaqw/yYmXG45PLw2cDmIq5epTz6jUQ8scsn/0xx68lZSlv12aDXLvpkOP7A SHakzl6G/zKjtz0kUCdQH7VHJPXgqw3089shinVDHqfeICG8VmAl6OUhrh3mIjiW94wVSq2+SOI jABw5d+NOLIhmfixiHAhOe6pfKeIdbTLZ9n3D+MDslX8M6vZ6rJcyt4OT6NtqQwjWLqd2+HoZ9E 4CVkuRqbSR+O4ue2gBQijmJRsDmh1s/RFZx4uUy7pDnwwdGzZ0nWjonsWcoomIstMm6m85ca+jL CkIx8diYz1iVHWp7FJGTs+T19twcD6PWB4sNqPNd7V3D+eqv+ltfFQTpEFsjdbX21o7eh9SvlOl 9bAZSok+VhNPQwsIBw51nNFO9Gr8e6tFJ2UpsD8/9u50OXji6Wje44rYRE849aZD/j8mk8H0Lq7 CwQfd5zFy+sqU1iRF1u052cPiYP4AwxBaV0AxnZBElW7ziXkoORD1GV4hKW25jQA+Rn3FU5FLk8 +5nnXGofIQTOh49znZ0dayW5Em/JJMrn1YJDQReFI0sGEsrZiz+YVeyOMv6vM7iUN5ZPn+qE15C f9q9MeFpxmxgtu+uROG6L7aNjFXh3vEiycOMTLT/aGr1WFmXk5hwdV/akBPK9oQU9rjiiuJd9I7 rmPs= X-Received: by 2002:a05:6102:418d:b0:7cc:3eb0:6ef5 with SMTP id ada2fe7eead31-7d03a4baae8mr2817813137.11.1793280441684; Thu, 29 Oct 2026 06:27:21 -0700 (PDT) Received: from localhost ([144.121.52.163]) by smtp.gmail.com with ESMTPSA id a1e0cc1a2514c-99125931983sm9476046241.2.2026.10.29.06.27.16 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 29 Oct 2026 06:27:20 -0700 (PDT) From: Ray Strode To: libcamera-devel@lists.libcamera.org Cc: kieran.bingham@ideasonboard.com, bod.linux@nxsw.ie, laurent.pinchart@ideasonboard.com, stefan.klug@ideasonboard.com, martincarvalho@gmail.com Subject: [PATCH v4 7/9] tests: Validate native sensor crop coordinates Date: Thu, 29 Oct 2026 09:26:29 -0400 Message-ID: <20261029132631.370507-8-halfline@gmail.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261029132631.370507-1-halfline@gmail.com> References: <20261016202818.1405645-1-halfline@gmail.com> <20261029132631.370507-1-halfline@gmail.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" From: Ray Strode This commit checks the conversion from debayer crop rectangles to native sensor coordinates in the existing geometry unit test. The new cases cover GPU and CPU crop sizes, physical pixel array origins, binning, flips, transposition and fractional coordinates. An odd image boundary checks that the conversion preserves image geometry before Bayer statistics alignment. Signed-off-by: Ray Strode --- test/debayer-geometry.cpp | 45 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/test/debayer-geometry.cpp b/test/debayer-geometry.cpp index cfc757fa..f2f0bc09 100644 --- a/test/debayer-geometry.cpp +++ b/test/debayer-geometry.cpp @@ -1,63 +1,106 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ /* * Copyright (C) 2026, Red Hat Inc. * * Sensor crop tests */ #include #include #include "libcamera/internal/software_isp/debayer_geometry.h" #include "test.h" using namespace libcamera; class DebayerGeometryTest : public Test { protected: int run() override { - if (testCrop()) + if (testCrop() || testSensorCrop()) return TestFail; return TestPass; } private: int testCrop() { static const struct { Size input; Size native; Size output; Rectangle expected; } cases[] = { { { 1920, 1080 }, { 1920, 1080 }, { 1920, 1080 }, { 0, 0, 1920, 1080 } }, { { 1928, 1092 }, { 1920, 1092 }, { 1920, 1092 }, { 4, 0, 1920, 1092 } }, /* Output resolution changes must not shrink the sensor window. */ { { 1928, 1092 }, { 1920, 1092 }, { 1920, 1080 }, { 4, 6, 1920, 1080 } }, { { 1928, 1092 }, { 1920, 1092 }, { 1280, 720 }, { 4, 6, 1920, 1080 } }, { { 1928, 1092 }, { 1920, 1092 }, { 640, 480 }, { 236, 0, 1456, 1092 } }, { { 1928, 1092 }, { 1920, 1092 }, { 512, 768 }, { 600, 0, 728, 1092 } }, { { 1928, 1092 }, { 1920, 1092 }, { 1280, 640 }, { 4, 66, 1920, 960 } }, /* A smaller Bayer border leaves an odd top coordinate. */ { { 1928, 1092 }, { 1924, 1092 }, { 1280, 720 }, { 2, 5, 1924, 1082 } }, }; for (const auto &entry : cases) { Rectangle crop = debayerCrop(entry.input, entry.native, entry.output); if (crop != entry.expected) { std::cerr << "Crop " << entry.input << " -> " << entry.output << " is " << crop << ", expected " << entry.expected << std::endl; return TestFail; } } return TestPass; } + + int testSensorCrop() + { + static const struct { + Rectangle crop; + Size input; + Rectangle analog; + Transform transform; + Rectangle expected; + } cases[] = { + /* GPU scaling keeps the full visible sensor region. */ + { { 4, 6, 1920, 1080 }, { 1928, 1092 }, { 0, 0, 1928, 1092 }, Transform::Identity, { 4, 6, 1920, 1080 } }, + /* A full readout keeps its physical pixel-array origin. */ + { { 0, 0, 2000, 1500 }, { 2000, 1500 }, { 8, 8, 4000, 3000 }, Transform::Identity, { 8, 8, 4000, 3000 } }, + /* Sensor crop offsets and 2x binning use native pixels. */ + { { 4, 6, 1920, 1080 }, { 1928, 1092 }, { 100, 200, 3856, 2184 }, Transform::Identity, { 108, 212, 3840, 2160 } }, + /* CPU debayering crops to output size without scaling. */ + { { 324, 186, 1280, 720 }, { 1928, 1092 }, { 100, 200, 3856, 2184 }, Transform::Identity, { 748, 572, 2560, 1440 } }, + /* Preserve an odd image boundary before statistics alignment. */ + { { 2, 5, 1924, 1082 }, { 1928, 1092 }, { 0, 0, 3856, 2184 }, Transform::Identity, { 4, 10, 3848, 2164 } }, + /* Undo sensor flips before reporting the native rectangle. */ + { { 2, 4, 20, 10 }, { 100, 60 }, { 10, 20, 200, 120 }, Transform::HFlip, { 166, 28, 40, 20 } }, + { { 2, 4, 20, 10 }, { 100, 60 }, { 10, 20, 200, 120 }, Transform::VFlip, { 14, 112, 40, 20 } }, + { { 2, 4, 20, 10 }, { 100, 60 }, { 10, 20, 200, 120 }, Transform::HVFlip, { 166, 112, 40, 20 } }, + /* Transposition is undone before the flip. */ + { { 4, 2, 10, 20 }, { 60, 100 }, { 10, 20, 200, 120 }, Transform::Rot270, { 166, 28, 40, 20 } }, + /* Fractional native coordinates and sizes truncate independently. */ + { { 1, 1, 3, 3 }, { 8, 8 }, { 10, 20, 12, 12 }, Transform::Identity, { 11, 21, 4, 4 } }, + }; + + for (const auto &entry : cases) { + Rectangle crop = debayerCropToSensor(entry.crop, entry.input, + entry.analog, + entry.transform); + if (crop != entry.expected) { + std::cerr << "Sensor mapping of " << entry.crop << " in " << entry.input + << " is " << crop << ", expected " << entry.expected << std::endl; + return TestFail; + } + } + + return TestPass; + } }; TEST_REGISTER(DebayerGeometryTest) From patchwork Thu Oct 29 13:26:30 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ray Strode X-Patchwork-Id: 28700 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 6ABBCC3261 for ; Thu, 29 Oct 2026 13:27:30 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 1CAD3690BB; Thu, 29 Oct 2026 14:27:30 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b="L2MeTblX"; dkim-atps=neutral Received: from mail-ua1-x92c.google.com (mail-ua1-x92c.google.com [IPv6:2607:f8b0:4864:20::92c]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id C1DD5690B2 for ; Thu, 29 Oct 2026 14:27:26 +0100 (CET) Received: by mail-ua1-x92c.google.com with SMTP id a1e0cc1a2514c-9828fee1b4bso745218241.3 for ; Thu, 29 Oct 2026 06:27:26 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20260707; t=1793280445; x=1793885245; 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=fpp5D11xjW5GknMdc9JoAnpOrym1MSWo25gamzd6rHg=; b=L2MeTblXddGL6xoph1HGf1e3Q7sDEeN3L3+rSMzNOGlX4Xq+16hmznjFAWmZK+2jlQ /GBLhd7vhryBS/vvbzrVfZl4Nt8y4bpSh1V5UKpJxIENTTC/3OmZUv8UGoOap0yPeZQf +rMAydE0g2cruRs9+ICCobLhOit+CXiUlLV08xWotb7xnBDI4sVIE9dZIpWSTJy2AUx0 NijlIlhKQrM55dcVLMl8JnHkBKRKQyI6+hqVCS1p3GsFP/cEE9vhiogSLPoGUkufneis SGOl7ashrZKssOiCsVyzWCNOmkw3ckeIQ4+vaqonytVGhJWuLkt270MHkUZWHZe//5QE XTEA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1793280445; x=1793885245; 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=fpp5D11xjW5GknMdc9JoAnpOrym1MSWo25gamzd6rHg=; b=LZH0dgujp7K9DuRtq5KtcU6k03jihUtjWZLy4WF3dpBNG6/xeJ/K29YlyeW2FEGOhX eCTrxsHDK1ugWo2OW8NnrdeLt+JSKTQnrdBtBCX1moslNOZhEbOcVwRm0gAhgWIThBKo cDDgpPSud6rKxE9Nv5TuqJ1ELFI963ViHB9nEtHEbe0D7RQgupK2YkM+BSXp3+8yRVoR cqSgHc380yryaTmmqZ/SI2Sd+K5vPhmGbClgUqSQKu9jTK9886Mg4XvicuDdqyXNuC5p pxB8CU9TZkIEIlnB0QwtY/esmCfdymkFSnwANNsMOW/O+tbWrOQuGRoRSASFAtby5A3k kwkw== X-Gm-Message-State: AFq9FYKjoHq4A2UHpqlIUe4PqBTycGLB570+2KVpd8jjMCLrBVPfxJS0 A9Up0CNogjxMBmWFVptHTx4vgUgT4KMSVFTMhnpoeXDUmjMr4nEIIJqV1WZCldsPBYQ= X-Gm-Gg: AYBFou00azBNgKTfueN/uvppmCiWO3kqzkMgHmDg5MBYMtJrZyDVYwZM3sw4ggpaV3s kAgv+bkjaGEZtXZRxN60je4DS1AOYJV65O2/DGnwMZx58SCsm5tFj3lzhcXxfc0T/Id2TFW9sh1 CuOKMiqkgeTw+I7DRTisLcczuffVjg9aKwdtLRU5aS7iUVDftFoA1lkybFRD2AlJWx4DWvgPbeh LHOIfg321zpwvjZw9U9D1JqoX+gxYk4bvFML6ibW2aQMKCPNE01DYRZs3ZYa1ODHA9febFEbXwe zPmMOu3ktTivtja9u6vmFno7vG7uo80PyC2cAUZ2gwugBx0trhnKwiojm97uxsmu/bxUi/JO1y+ Jh12iVG/NEh7rTMDmw4XXIz+Q9CSSDgrgVfCg/9VMmwwXG6IZMCXOxLVX8o9rKgEaJVdmT3CV3n x3r+mTfARaXcBDkf6na3yVLUvpQbEGfLcDWF9WSsHa2aVJYzHOvRtEYC51jkr+pU2bwqPTaBANX 10lMjs8lhOO8mpQGh79XPYEK0ML93GhGCDGLodeelewJ6LnqSo7oxjnk1Ddumhbj+zGSc1RQlzY v6qUAIapHDFgDakPrR09S6cqZrJ1rsybO2yjo4mgJwG4Og3lVNPNCqGz X-Received: by 2002:a05:6102:605a:b0:7cc:32da:56e with SMTP id ada2fe7eead31-7d03a5a2346mr1967555137.19.1793280445510; Thu, 29 Oct 2026 06:27:25 -0700 (PDT) Received: from localhost ([144.121.52.163]) by smtp.gmail.com with ESMTPSA id a1e0cc1a2514c-99125931983sm9476046241.2.2026.10.29.06.27.22 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 29 Oct 2026 06:27:24 -0700 (PDT) From: Ray Strode To: libcamera-devel@lists.libcamera.org Cc: kieran.bingham@ideasonboard.com, bod.linux@nxsw.ie, laurent.pinchart@ideasonboard.com, stefan.klug@ideasonboard.com, martincarvalho@gmail.com Subject: [PATCH v4 8/9] pipeline: simple: Report the software ISP's maximum crop Date: Thu, 29 Oct 2026 09:26:30 -0400 Message-ID: <20261029132631.370507-9-halfline@gmail.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261029132631.370507-1-halfline@gmail.com> References: <20261016202818.1405645-1-halfline@gmail.com> <20261029132631.370507-1-halfline@gmail.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" From: Ray Strode This commit reports the software ISP's configured image region through the ScalerCropMaximum camera property. The simple pipeline currently exposes sensor properties without identifying the region retained by software processing. CPU debayering crops to the output dimensions, while EGL also scales the image, so the visible region can be smaller than the analog sensor crop. The reported rectangle uses physical pixel array coordinates in native sensor pixels. Require matching sensor, capture and intermediate pad sizes, and full-frame crop and compose selections where available. Unavailable selection stages preserve the preceding rectangle on supported pass-through paths. Query errors or reported intermediate cropping or scaling leave the mapping unknown and the property unreported. Update ScalerCropMaximum only after configuration succeeds, removing it when the new configuration cannot report a software ISP crop. Based on Kieran Bingham's ScalerCropMaximum reporting patch, extended to account for the software ISP's selected image region. Link: https://patchwork.libcamera.org/patch/26678/ Signed-off-by: Ray Strode --- src/libcamera/pipeline/simple/simple.cpp | 92 +++++++++++++++++++++++- 1 file changed, 89 insertions(+), 3 deletions(-) diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp index 38e94a15..3e6a3a50 100644 --- a/src/libcamera/pipeline/simple/simple.cpp +++ b/src/libcamera/pipeline/simple/simple.cpp @@ -3,74 +3,76 @@ * Copyright (C) 2020, Laurent Pinchart * Copyright (C) 2019, Martijn Braam * * Pipeline handler for simple pipelines */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include #include "libcamera/internal/camera.h" #include "libcamera/internal/camera_manager.h" #include "libcamera/internal/camera_sensor.h" #include "libcamera/internal/camera_sensor_properties.h" #include "libcamera/internal/converter.h" #include "libcamera/internal/delayed_controls.h" #include "libcamera/internal/device_enumerator.h" #include "libcamera/internal/formats.h" #include "libcamera/internal/global_configuration.h" #include "libcamera/internal/media_device.h" #include "libcamera/internal/pipeline_handler.h" #include "libcamera/internal/request.h" +#include "libcamera/internal/software_isp/debayer_geometry.h" #include "libcamera/internal/software_isp/software_isp.h" #include "libcamera/internal/v4l2_subdevice.h" #include "libcamera/internal/v4l2_videodevice.h" namespace libcamera { LOG_DEFINE_CATEGORY(SimplePipeline) /* ----------------------------------------------------------------------------- * * Overview * -------- * * The SimplePipelineHandler relies on generic kernel APIs to control a camera * device, without any device-specific code and with limited device-specific * static data. * * To qualify for support by the simple pipeline handler, a device shall * * - be supported by V4L2 drivers, exposing the Media Controller API, the V4L2 * subdev APIs and the media bus format-based enumeration extension for the * VIDIOC_ENUM_FMT ioctl ; * - not expose any device-specific API from drivers to userspace ; * - include one or more camera sensor media entities and one or more video * capture devices ; * - have a capture pipeline with linear paths from the camera sensors to the * video capture devices ; and * - have an optional memory-to-memory device to perform format conversion * and/or scaling, exposed as a V4L2 M2M device. * @@ -265,60 +267,62 @@ static const SimplePipelineInfo supportedDevices[] = { { "mxc-isi", {}, false }, { "qcom-camss", {}, true }, { "sun6i-csi", {}, false }, }; bool isRaw(const StreamConfiguration &cfg) { return libcamera::PixelFormatInfo::info(cfg.pixelFormat).colourEncoding == libcamera::PixelFormatInfo::ColourEncodingRAW; } } /* namespace */ class SimpleCameraData : public Camera::Private { public: SimpleCameraData(SimplePipelineHandler *pipe, unsigned int numStreams, MediaEntity *sensor); bool isValid() const { return sensor_ != nullptr; } SimplePipelineHandler *pipe(); int init(); int setupLinks(); int setupFormats(V4L2SubdeviceFormat *format, V4L2Subdevice::Whence whence, Transform transform = Transform::Identity); void imageBufferReady(FrameBuffer *buffer); void clearIncompleteRequests(); + std::optional computeScalerCrop(const Size &inputSize, Transform transform); + void updateScalerCrop(std::optional crop); unsigned int streamIndex(const Stream *stream) const { return stream - &streams_.front(); } struct Entity { /* The media entity, always valid. */ MediaEntity *entity; /* * Whether or not the entity is a subdev that supports the * routing API. */ bool supportsRouting; /* * The local sink pad connected to the upstream entity, null for * the camera sensor at the beginning of the pipeline. */ const MediaPad *sink; /* * The local source pad connected to the downstream entity, null * for the video node at the end of the pipeline. */ const MediaPad *source; /* * The link on the source pad, to the downstream entity, null * for the video node at the end of the pipeline. */ MediaLink *sourceLink; }; @@ -333,60 +337,61 @@ public: }; std::vector streams_; Stream *rawStream_; /* * All entities in the pipeline, from the camera sensor to the video * node. */ std::list entities_; std::unique_ptr sensor_; V4L2VideoDevice *video_; V4L2Subdevice *frameStartEmitter_; std::vector configs_; std::map> formats_; std::unique_ptr delayedCtrls_; std::vector> conversionBuffers_; struct RequestOutputs { Request *request; std::map outputs; }; std::queue conversionQueue_; bool useConversion_; std::unique_ptr converter_; std::unique_ptr swIsp_; SimpleFrames frameInfo_; + std::optional scalerCrop_; private: void tryPipeline(unsigned int code, const Size &size); static std::vector routedSourcePads(MediaPad *sink); void tryCompleteRequest(Request *request); void conversionInputDone(FrameBuffer *buffer); void conversionOutputDone(FrameBuffer *buffer); void ispStatsReady(uint32_t frame, uint32_t bufferId); void metadataReady(uint32_t frame, const ControlList &metadata); void setSensorControls(const ControlList &sensorControls); }; class SimpleCameraConfiguration : public CameraConfiguration { public: SimpleCameraConfiguration(Camera *camera, SimpleCameraData *data); Status validate() override; const SimpleCameraData::Configuration *pipeConfig() const { return pipeConfig_; } bool needConversion() const { return needConversion_; } const Transform &combinedTransform() const { return combinedTransform_; } private: @@ -956,60 +961,130 @@ void SimpleCameraData::imageBufferReady(FrameBuffer *buffer) } if (converter_) converter_->queueBuffers(buffer, conversionQueue_.front().outputs); else /* * request->sequence() cannot be retrieved from `buffer' inside * queueBuffers because unique_ptr's make buffer->request() invalid * already here. */ swIsp_->queueBuffers(request->sequence(), buffer, conversionQueue_.front().outputs); conversionQueue_.pop(); return; } /* Otherwise simply complete the request. */ pipe->completeBuffer(request, buffer); tryCompleteRequest(request); } void SimpleCameraData::clearIncompleteRequests() { while (!conversionQueue_.empty()) { pipe()->cancelRequest(conversionQueue_.front().request); conversionQueue_.pop(); } } +std::optional SimpleCameraData::computeScalerCrop(const Size &inputSize, + Transform transform) +{ + IPACameraSensorInfo sensorInfo; + if (sensor_->sensorInfo(&sensorInfo)) + return std::nullopt; + + /* + * The buffer-to-sensor mapping requires a capture path that preserves + * the sensor output. Intermediate cropping or scaling needs a separate + * mapping; do not report the analog crop as if it included that work. + * Unavailable selection stages preserve the preceding rectangle on the + * supported pass-through paths, provided the pad formats match. + */ + if (sensorInfo.outputSize != inputSize) + return std::nullopt; + + for (const Entity &entity : entities_) { + if (!entity.sink || !entity.source) + continue; + + V4L2Subdevice *subdev = pipe()->subdev(entity.entity); + for (const MediaPad *pad : { entity.sink, entity.source }) { + V4L2SubdeviceFormat format{}; + if (subdev->getFormat(pad->index(), &format) || + format.size != inputSize) + return std::nullopt; + + for (unsigned int target : { V4L2_SEL_TGT_CROP, V4L2_SEL_TGT_COMPOSE }) { + /* Composition is applied on the sink pad. */ + if (pad == entity.source && target == V4L2_SEL_TGT_COMPOSE) + continue; + + std::optional selection; + int ret = subdev->getSelection(pad->index(), target, &selection); + if (ret) + return std::nullopt; + if (!selection) + continue; + + if (*selection != Rectangle(inputSize)) + return std::nullopt; + } + } + } + + return debayerCropToSensor(swIsp_->inputCrop(), inputSize, + sensorInfo.analogCrop, transform); +} + +void SimpleCameraData::updateScalerCrop(std::optional crop) +{ + scalerCrop_ = crop; + if (crop) { + properties_.set(properties::ScalerCropMaximum, *crop); + return; + } + + if (!properties_.contains(properties::ScalerCropMaximum.id())) + return; + + ControlList updatedProperties = properties_; + updatedProperties.clear(); + for (const auto &[id, value] : properties_) { + if (id != properties::ScalerCropMaximum.id()) + updatedProperties.set(id, value); + } + properties_ = std::move(updatedProperties); +} + void SimpleCameraData::tryCompleteRequest(Request *request) { if (request->hasPendingBuffers()) return; SimpleFrameInfo *info = frameInfo_.find(request->sequence()); if (!info) { /* Something is really wrong, let's return. */ return; } if (info->metadataRequired && !info->metadataProcessed) return; frameInfo_.destroy(info->frame); pipe()->completeRequest(request); } void SimpleCameraData::conversionInputDone(FrameBuffer *buffer) { if (rawStream_) { /* Complete the input buffer as with raw-only processing. */ Request *request = buffer->request(); if (pipe()->completeBuffer(request, buffer)) tryCompleteRequest(request); } else { /* Queue the input buffer back for capture. */ video_->queueBuffer(buffer); } } @@ -1576,76 +1651,87 @@ int SimplePipelineHandler::configure(Camera *camera, CameraConfiguration *c) << pipeConfig->captureSize << "-" << videoFormat << " (got " << captureFormat << ")"; return -EINVAL; } if (requested_bpl && captureFormat.planes[0].bpl != requested_bpl) { LOG(SimplePipeline, Info) << "Input buffer stride ignored by the driver. " << "Requested " << requested_bpl << ", got " << captureFormat.planes[0].bpl; } /* Configure the converter if needed. */ std::vector> outputCfgs; data->useConversion_ = config->needConversion(); data->rawStream_ = nullptr; for (unsigned int i = 0; i < config->size(); ++i) { StreamConfiguration &cfg = config->at(i); bool rawStream = isRaw(cfg); cfg.setStream(&data->streams_[i]); if (data->useConversion_ && !rawStream) outputCfgs.push_back(cfg); if (rawStream) data->rawStream_ = &data->streams_[i]; } - if (outputCfgs.empty()) + if (outputCfgs.empty()) { + data->updateScalerCrop(std::nullopt); return 0; + } StreamConfiguration inputCfg; inputCfg.pixelFormat = videoFormat.toPixelFormat(); inputCfg.size = pipeConfig->captureSize; inputCfg.stride = captureFormat.planes[0].bpl; inputCfg.bufferCount = kNumInternalBuffers; if (data->converter_) { - return data->converter_->configure(inputCfg, outputCfgs); + ret = data->converter_->configure(inputCfg, outputCfgs); } else { ipa::softisp::IPAConfigInfo configInfo; configInfo.sensorControls = data->sensor_->controls(); - return data->swIsp_->configure(inputCfg, outputCfgs, configInfo, &data->controlInfo_); + ret = data->swIsp_->configure(inputCfg, outputCfgs, configInfo, &data->controlInfo_); } + if (ret) + return ret; + + std::optional crop; + if (!data->converter_) + crop = data->computeScalerCrop(inputCfg.size, config->combinedTransform()); + data->updateScalerCrop(crop); + + return 0; } int SimplePipelineHandler::exportFrameBuffers(Camera *camera, Stream *stream, std::vector> *buffers) { SimpleCameraData *data = cameraData(camera); unsigned int count = stream->configuration().bufferCount; /* * Export buffers on the converter or capture video node, depending on * whether the converter is used or not. */ if (data->useConversion_ && stream != data->rawStream_) return data->converter_ ? data->converter_->exportBuffers(stream, count, buffers) : data->swIsp_->exportBuffers(stream, count, buffers); else return data->video_->exportBuffers(count, buffers); } int SimplePipelineHandler::start(Camera *camera, [[maybe_unused]] const ControlList *controls) { SimpleCameraData *data = cameraData(camera); V4L2VideoDevice *video = data->video_; V4L2Subdevice *frameStartEmitter = data->frameStartEmitter_; int ret; const MediaPad *pad = acquirePipeline(data); if (pad) { LOG(SimplePipeline, Info) From patchwork Thu Oct 29 13:26:31 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ray Strode X-Patchwork-Id: 28701 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 56D0EC3261 for ; Thu, 29 Oct 2026 13:27:36 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 0C897690B2; Thu, 29 Oct 2026 14:27:36 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b="U6WhGJ52"; dkim-atps=neutral Received: from mail-ua1-x936.google.com (mail-ua1-x936.google.com [IPv6:2607:f8b0:4864:20::936]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id E7AF66909D for ; Thu, 29 Oct 2026 14:27:33 +0100 (CET) Received: by mail-ua1-x936.google.com with SMTP id a1e0cc1a2514c-98c7798ce74so685958241.1 for ; Thu, 29 Oct 2026 06:27:33 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20260707; t=1793280453; x=1793885253; 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=LcQL7aIUPoMCtRE7rLSaqztgaQ013sZBw1GwMYo0zZI=; b=U6WhGJ52eoX/am2PTmmH+EWwsqfu8c9TgZewOFVf3bORjTQdfHWLx/TYR33fOyC3Ba FDyrt0a5lBYiyzJmyLMfCQuQV2BK3u3VEg9t+YWQ3xc8jwCkHJfDtdKgVGbb/8NFSJ0U 5x/abkskYXdqWN6uZ29P4yQk7C2fyrMcV85sT1zLk8S5RRReuZjnpFlLBvp+H/m3FDEl OleiQyLtxQDO/0M32BIlgfwSex3IlRWkh4NvZ4FHtrJFacHzcucT/Qk1W9aOvQVL7Tmf fVmskDrq9+esxkChYp3MgpKgUTlDYdmTDnY4n06KT0qLgYoQBio0LNjaXxQtc6ZvfCKy U1bg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1793280453; x=1793885253; 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=LcQL7aIUPoMCtRE7rLSaqztgaQ013sZBw1GwMYo0zZI=; b=ujGauwnaII5SiYC4hMKfftvQmi7EJkfPI68E653vyQEbojFjUEO46Hw0nm9BKwQa8t aAdkgaEg2+ER69d7P3i8gv4ulew+9Xn04ukzxQEm8u2HzLDqa5HPA+vhUe4lP7ekwIKi 8zsGu3qtjvMotYeoRXLmyNFGCDQ2Ue8JWDisFZbA9MuvmT39BFDfJ8PSzWYdO9eU6+jO paZd4TqPTlj/YEw3dSlmwL/IMAe0cukR13ZXchFkgu/bhVmgqtm4gSagowXcxI6R7RPs kfrqA3rEN2Zo1elN/d9i6/7jNr6epEa0Ixo/QZUByejUIEpL0RbSiLiOJUjOpvEUc1/d /whA== X-Gm-Message-State: AFq9FYI2ZWQ/X/otMIIrT6TOmngwu0dJVvplZnznGDSMhVsa/444Py/b uWHHlj22nLUwRogMPIIJZNV8J38XCT0aGwMl/vxhXHpFGnlIxG6XhIywARRaGjHlDWg= X-Gm-Gg: AYBFou1zCn8x/jt4b5IpjT9P4tPqDPobhSl4XnaNkmduVlZC25D80I3tETPtZZsUgy5 1nl2RuEQXNKhMKxlEQR8G4gyMIduXvcfZvi3YucoeH4rCNz8S00QTP/DX7yMI0Gny09XNjr19Aq nKq5r8tPnu+X+tD34+x/rZX1VbgVpwE8QfCKFjgqkjBX07lugy814NhABQkmwzjTMvqgT7Zqr+Q F4Oqa0RsuTKG4KPeJ9Z5Et4AB20atbEaaeFULpAKYmw8h0YlBH0nELFDO3iC/y2ki2ZAHnznb0M EooEymA7RS8spCf5QxlMPbzK/dbozRaRNgMjHvTOAY9WWKPsNZC1Ddi8IabHtdlkKt1/EUv6bz/ BDeAjfySFMtO/1sS+Q/mfP/HT2bPoPIU8jOqtbyFXHgctPyycvAgPYTf6xY8HYjpzAF5tqTwaPo dyvZt35zyZBe3Xy3JoR/NJEdqgk8FkcXEPOFknydB/ZcVvFU7dnv8Xvt0o6aydX5ZOBrXNO2138 GSXWzS7S3lhShyk3aA9h0C4KrdhmyzW9gTK9LbBBlqrjJ5zMDw9OmHGD84oUYnZQ/BL2vpv3Y2+ bJFj9OqH3QXQeD6VzFP0/xRQ/FYo2NYmsIsT6Djpim6V2fz1e1yvPo5dcw== X-Received: by 2002:a05:6102:f0f:b0:7c0:1d48:b2a2 with SMTP id ada2fe7eead31-7d03a633e1cmr2278058137.31.1793280452741; Thu, 29 Oct 2026 06:27:32 -0700 (PDT) Received: from localhost ([144.121.52.163]) by smtp.gmail.com with ESMTPSA id a1e0cc1a2514c-99125931983sm9476046241.2.2026.10.29.06.27.25 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 29 Oct 2026 06:27:29 -0700 (PDT) From: Ray Strode To: libcamera-devel@lists.libcamera.org Cc: kieran.bingham@ideasonboard.com, bod.linux@nxsw.ie, laurent.pinchart@ideasonboard.com, stefan.klug@ideasonboard.com, martincarvalho@gmail.com Subject: [PATCH v4 9/9] pipeline: simple: Include the software ISP crop in request metadata Date: Thu, 29 Oct 2026 09:26:31 -0400 Message-ID: <20261029132631.370507-10-halfline@gmail.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261029132631.370507-1-halfline@gmail.com> References: <20261016202818.1405645-1-halfline@gmail.com> <20261029132631.370507-1-halfline@gmail.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" From: Ray Strode This commit includes the software ISP's configured image region in completed requests as ScalerCrop metadata. The simple pipeline exposes this fixed region through ScalerCropMaximum, but completed requests still omit the sensor rectangle visible in their processed output. The crop is cached when the software ISP is configured and reported when each request completes. Successful reconfiguration replaces or clears the cached region so raw-only requests do not inherit an earlier processed-image crop. Based on Kieran Bingham's ScalerCrop metadata reporting patch, extended to use the software ISP's selected image region. Link: https://patchwork.libcamera.org/patch/26817/ Signed-off-by: Ray Strode --- src/libcamera/pipeline/simple/simple.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp index 3e6a3a50..4196ad4b 100644 --- a/src/libcamera/pipeline/simple/simple.cpp +++ b/src/libcamera/pipeline/simple/simple.cpp @@ -1045,60 +1045,63 @@ void SimpleCameraData::updateScalerCrop(std::optional crop) properties_.set(properties::ScalerCropMaximum, *crop); return; } if (!properties_.contains(properties::ScalerCropMaximum.id())) return; ControlList updatedProperties = properties_; updatedProperties.clear(); for (const auto &[id, value] : properties_) { if (id != properties::ScalerCropMaximum.id()) updatedProperties.set(id, value); } properties_ = std::move(updatedProperties); } void SimpleCameraData::tryCompleteRequest(Request *request) { if (request->hasPendingBuffers()) return; SimpleFrameInfo *info = frameInfo_.find(request->sequence()); if (!info) { /* Something is really wrong, let's return. */ return; } if (info->metadataRequired && !info->metadataProcessed) return; + if (scalerCrop_) + request->_d()->metadata().set(controls::ScalerCrop, *scalerCrop_); + frameInfo_.destroy(info->frame); pipe()->completeRequest(request); } void SimpleCameraData::conversionInputDone(FrameBuffer *buffer) { if (rawStream_) { /* Complete the input buffer as with raw-only processing. */ Request *request = buffer->request(); if (pipe()->completeBuffer(request, buffer)) tryCompleteRequest(request); } else { /* Queue the input buffer back for capture. */ video_->queueBuffer(buffer); } } void SimpleCameraData::conversionOutputDone(FrameBuffer *buffer) { SimplePipelineHandler *pipe = SimpleCameraData::pipe(); /* Complete the buffer and the request. */ Request *request = buffer->request(); if (pipe->completeBuffer(request, buffer)) tryCompleteRequest(request); } void SimpleCameraData::ispStatsReady(uint32_t frame, uint32_t bufferId) { swIsp_->processStats(frame, bufferId,