From patchwork Thu Oct 29 15:20:48 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ray Strode X-Patchwork-Id: 28704 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 84238C324C for ; Thu, 29 Oct 2026 15:21:23 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 432AF690BE; Thu, 29 Oct 2026 16:21:23 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b="IXyGhsWV"; 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 D2D06690A0 for ; Thu, 29 Oct 2026 16:21:21 +0100 (CET) Received: by mail-vs1-xe36.google.com with SMTP id ada2fe7eead31-7cb6dc850c7so739363137.0 for ; Thu, 29 Oct 2026 08:21:21 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20260707; t=1793287281; x=1793892081; 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=775dAeNj3d+9tofvdDChErbHq5b5F/zWMVc7jTD0eN4=; b=IXyGhsWVFuMnSAfjucTB3nNCU5Qloh11DDV+Ra9EM4aW4HpbuY8fU7kq5thqKOskhw k0EHGVFmqQxrhjxfktz3vKppH9qaFu/3az1Z+LmoXlynBYQjiw3ZlFgtjEur6AwKDzJJ cYz1TY+Sk4K2VdNbwdMxfI5hoY02Zmbc2skvb16uJyWkMWfAQcEWwn7dUP/NsEBsmhzv hwwds36DTvVjsSyE6HjHwAChEVG7rfyeOrP7Lv0OFGHHQr+sWA/soSburv1xLyQo2zEZ IiN+212WmyzqSeUjwqZS+T/vLqtIeGcsYvFaZhZAdxVVUAQ+FXOUJzR6npmm/UQ5Oh0d ufHA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1793287281; x=1793892081; 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=775dAeNj3d+9tofvdDChErbHq5b5F/zWMVc7jTD0eN4=; b=iVBNYx3jXIHBT4fObwRIby7KOTY17nlysgiEErq7yQBSn1qKjvyP4OsMQCvD/ed7pC ZZ4c6svt9kPy81X8SNmNDaNGyctJ83Yi01ZDs5/HIfvpR0p1BMs6bEPqz7X6a6Fr6TU0 3nniWS05ZNFzu5aMEPLkWQxMWhfydSv8gNu19QbPJgF04ZK+fFEF755/qUaajJWdaqHC PqfBQdbtoKX+jIu4jo8ITXl2lKe+BTcHqxPz5HbgH1xlHSxYTa3gwPPdxW305IJdrHOa +9/t/Q9xJESp9Vmja19gMrE+CYXNYwXTNKowdOulWjc8eu6oUD9p7gR8arPq4DFO8Jvg WnoA== X-Gm-Message-State: AFq9FYLLW+leUGF2/RVrH/AKPh5JhVw8dwvMRYcnl3fOb8KrY3vafz92 bKiIynNognyrhtfvBUDAqwVKao6ga4s0upRqJ5wHFcQeWLT7WxzIZ+0AYz4ZSAEeqfY= X-Gm-Gg: AYBFou1/HGAIIiA0EtMKv0mz32ShL0GSIwV+b82ofPzsonYqNgKyEJ3vQeq4MmyuDQ7 U20zfezF25LjnE03BT6l3fupI6LtLv3n7rHq1uKfad47Bm8edYNJMYGMTBo1B649toxVTJ9GiCT Yg4Yb3f5bp002XQ1C0OOWRlCfFKjz4fk3XuNK5h5RVFkdKGYn7J5F23y9f9SbmUX1fbZoVZiZ/o 5gaPWN7vVHEVzmWRRPl3m72k5hTvtANyjoen+tCR7U3i1CIgnE0oodkW6U8jIVTcbN8DNdQe8De NTozSCot4NSXt812wFr4wakEKk/LBGhSMSbrHwXpYrrJTLxzlqYuSkYLTHqex9Cyr1ePBV0UKf8 tb9/fiwoyby1e+jNMuZTeu/bcbXibT9oRbmlXLaelN7OwbdBmAvpR4Q0VLDOrh/0vsvQ2uW1ULi eBUQGB5AjNDjZgtVgToe7xG/rhzCNIONF1H4zMjqn5GRefJyroaaUFK3wMRFwGYNfMbUDfJ0V17 GFNj4a0cd/ye29MHPh0hFN6VYYjuW3o3sntsQrtafR8SivtrK6Ct5hdlwGs8NrGJuLLKUcMQrya ZWSai3VSJ5F087RVnGHs/0NFbPAffRZUGH5Kvi41WpD1eLC4DvYsTWQD X-Received: by 2002:a67:e111:0:b0:7a7:195a:1b2a with SMTP id ada2fe7eead31-7d03a66be15mr2025381137.36.1793287279499; Thu, 29 Oct 2026 08:21:19 -0700 (PDT) Received: from localhost ([144.121.52.163]) by smtp.gmail.com with ESMTPSA id a1e0cc1a2514c-991f74e5e22sm206563241.4.2026.10.29.08.21.14 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 29 Oct 2026 08:21:17 -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 v5 01/11] libcamera: software_isp: Centre the crop the GPU debayer applies Date: Thu, 29 Oct 2026 11:20:48 -0400 Message-ID: <20261029152058.896209-2-halfline@gmail.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261029152058.896209-1-halfline@gmail.com> References: <20261029152058.896209-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: Martin Neiva de Carvalho A stream whose aspect ratio differs from the sensor's cannot show all of it, and cropping the sides is the right answer - black bars would be worse. But the crop has to be taken from both sides. The viewport is sized from the sensor frame while the buffer being drawn into is the size the application asked for, so that buffer covers only the corner of the viewport running from NDC -1 to where its own width reaches. Translating the quad by -(1 - scale) puts the texture's left and bottom edges exactly at that corner, so everything cropped comes off one side. Measured on an OV02C10 by registering a 640x480 stream against a 1920x1080 one of the same scene - searching for the scale and offset that maximise the normalised correlation between them - the smaller stream showed a 1391x1043 window of the sensor's 1920x1080, starting at (48, 22). Centring the 4:3 window in the 16:9 frame calls for 1440x1080 starting at (240, 0). A subject had to sit a sixth of a frame off centre to appear centred, and the framing moved whenever the application chose a different resolution. Translate to the middle of the region the buffer actually covers instead. That region spans NDC -1 to 2 * out / viewport - 1, so its midpoint is out / viewport - 1. The same measurement then reports 1438x1079 starting at (243, 0), which is the centred window to within the three pixels the registration can resolve. At the native size this shares the eight columns and twelve rows of Bayer border between the two edges rather than taking them all off one, moving the picture by half a border. Nothing is resampled: the viewport and the scale factor are untouched. Signed-off-by: Martin Neiva de Carvalho Signed-off-by: Ray Strode --- src/libcamera/software_isp/debayer_egl.cpp | 23 +++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) base-commit: 6e19ca0fa7aa75aa965511072f43b5e50dec1904 diff --git a/src/libcamera/software_isp/debayer_egl.cpp b/src/libcamera/software_isp/debayer_egl.cpp index 33933b11..3356070c 100644 --- a/src/libcamera/software_isp/debayer_egl.cpp +++ b/src/libcamera/software_isp/debayer_egl.cpp @@ -373,70 +373,87 @@ std::vector DebayerEGL::formats(PixelFormat inputFormat) std::tuple DebayerEGL::strideAndFrameSize(const PixelFormat &outputFormat, const Size &size) { /* Align stride to 256 bytes as a generic GPU memory access alignment */ const PixelFormatInfo &info = PixelFormatInfo::info(outputFormat); return std::make_tuple(info.stride(size.width, 0, 256), info.frameSize(size, 256)); } uint32_t DebayerEGL::preferredInputStride(const PixelFormat &inputFormat, const Size &size) { const PixelFormatInfo &info = PixelFormatInfo::info(inputFormat); return info.stride(size.width, 0, 256); } void DebayerEGL::setShaderVariableValues(eGLImage &eglImageIn, const DebayerParams ¶ms) { /* * Raw Bayer 8-bit, and packed raw Bayer 10-bit/12-bit formats * are stored in a GL_LUMINANCE texture. The texture width is * equal to the stride. */ GLfloat firstRed[] = { firstRed_x_, firstRed_y_ }; GLfloat imgSize[] = { (GLfloat)width_, (GLfloat)height_ }; GLfloat Step[] = { static_cast(bytesPerPixel_) / (inputConfig_.stride - 1), 1.0f / (height_ - 1) }; GLfloat Stride = (GLfloat)width_ / (shaderStridePixels_ / bytesPerPixel_); /* * Scale the output size from the native size the algorithm produces for * the input size. Keep the aspect ratio and prefer cropping over black - * bars. + * bars - but crop from both sides, not from one. + * + * The viewport is the sensor frame while the buffer drawn into is the + * requested size, so the buffer covers only the corner of the viewport + * that starts at NDC -1 and ends where its own width reaches. Placing + * the quad at -(1 - scale) puts the texture's left edge exactly at + * that corner, so everything cropped is taken off one side: metered + * by registering a 640x480 stream against a 1920x1080 one of the same + * scene, the frame shown ran from x=48 rather than from the x=240 + * that centring the 4:3 window in a 16:9 sensor calls for. + * + * Centre it on the region the buffer actually covers instead. That + * region spans NDC -1 to 2*out/viewport - 1, whose midpoint is + * out/viewport - 1, and putting the quad's centre there splits the + * crop evenly between the two edges at every size, the native one + * included - where it shares the eight columns of Bayer border + * instead of taking them all off the right. */ GLfloat scale = std::max((GLfloat)outputSize_.width / nativeOutputSize_.width, (GLfloat)outputSize_.height / nativeOutputSize_.height); - GLfloat trans = -(1.0f - scale); + GLfloat transX = (GLfloat)outputSize_.width / width_ - 1.0f; + GLfloat transY = (GLfloat)outputSize_.height / height_ - 1.0f; GLfloat projMatrix[] = { scale, 0, 0, 0, 0, scale, 0, 0, 0, 0, 1, 0, - trans, trans, 0, 1 + transX, transY, 0, 1 }; /* Static const coordinates */ static const GLfloat vcoordinates[4][2] = { { -1.0f, -1.0f }, { -1.0f, +1.0f }, { +1.0f, +1.0f }, { +1.0f, -1.0f }, }; static const GLfloat tcoordinates[4][2] = { { 0.0f, 0.0f }, { 0.0f, 1.0f }, { 1.0f, 1.0f }, { 1.0f, 0.0f }, }; /* vertexIn - bayer_8.vert */ glEnableVertexAttribArray(attributeVertex_); glVertexAttribPointer(attributeVertex_, 2, GL_FLOAT, GL_TRUE, 2 * sizeof(GLfloat), vcoordinates); /* textureIn - bayer_8.vert */ glEnableVertexAttribArray(attributeTexture_); glVertexAttribPointer(attributeTexture_, 2, GL_FLOAT, GL_TRUE, 2 * sizeof(GLfloat), tcoordinates); /* * Set the sampler2D to the respective texture unit for each texutre * To simultaneously sample multiple textures we need to use multiple * texture units */ From patchwork Thu Oct 29 15:20:49 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ray Strode X-Patchwork-Id: 28705 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 93D8CC324C for ; Thu, 29 Oct 2026 15:21:28 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 46D6D690C5; Thu, 29 Oct 2026 16:21:28 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b="a6rEGwEO"; 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 44791690A0 for ; Thu, 29 Oct 2026 16:21:26 +0100 (CET) Received: by mail-ua1-x934.google.com with SMTP id a1e0cc1a2514c-9909e82513fso627503241.0 for ; Thu, 29 Oct 2026 08:21:26 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20260707; t=1793287285; x=1793892085; 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=IHti+fr/IQIHe4qZjtG5cAOj1d6oafxj1ukP7XMpcBs=; b=a6rEGwEObclmKiy4Qqy+ZM5UY94qQzyZ4iBxfeV0aXl4wK9Qyk+tRd3Zi8IHoMtn+q sKLpIfgBOVLIKtVa+A/mSSEckezPYF9Sy94zj22n2xsmh9GCvbDwCmsUpb9BL92BABEi rjpm1AyyUfErzDp1bOaZxJNXaQBZp5+jZNG7J9Ul773OvFVRhRgpV0/yDbEgsOWv6bc4 EMyxOs+oiIfE/V2Uehixam4ywdZhXpNwuLNRo1T2UtkO1C7kXZ7uiZxLBCh0xgcQnaQ2 9c5RdWNLx9F2RAw9cxvNvTWCCduQgjLI/U3P5XAVqPxnAQaIFTY4pIRZN5BuPZWiFO+a D2kQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1793287285; x=1793892085; 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=IHti+fr/IQIHe4qZjtG5cAOj1d6oafxj1ukP7XMpcBs=; b=GPh749HENxK27NVSLkkd3OaivUFM9vlO8APpxx24tXR8CF3PlWOsPaperJIUx+ZTJ5 3j1FbFglDOfu6u5az+dSCMV2GCsxEsOb5dXY9De3K+JAcXQxtHGoUfvwwh8iyheheufu ndE1bAdu1DExO8ukTKOS2eMShk00ukl5C2fLk16il0KlhvkRqdtEQjcDOeGZcup4UUEm GIHFhZWaYuhX+01u5rCv1R0Gz3EcOF3ioXvoHbGbApDc6uWJp4FQNtnYJ+rTQjKJA45S I4AdXXizsqfrYVwmKt+xU4dvpZnD7/4MouCwX+1msAaEdvYI3GP/+D5tb30WRlue2AHy elJw== X-Gm-Message-State: AFq9FYKAPov7yCHrUtAf8AzjItc33500B07dM2kVDXnbVeaRgPfBecYr Y20DqTEO2a8DL9bJC2JuzP3LxbuRVW0Zn6GGt3epi1fh4V4AVJoCb26kXWIqn3sE/XE= X-Gm-Gg: AYBFou3e3+eiBz9ibZ3jMqnLTGK8k4Oxt7XgeA560jKRn1ncF9OqYpO3qGWj9xOeIcx ZjIM/2xdHAXVlwjE1h+9DZKJE2Wyfe31GpLgFD6TdRuO8zb0NlP6mvM5emfYmVRg9+ONXOSiE6F gs66vPQsq6rv2alzat6SI047T6FPFpKZcZPwtqenjCeKwCZlD7ZjCpVXFIHM8xoWXsGeY+h+Rmt saxb7dWsJT7VnpBQk+kx+uCHEzxppo0IkAW6+C69GkNJ8lkJX6FZazxU5hWCBUec6VqKON40cFY MQZaXOM1YwQLugyF1H60vsLVWprSzU8CpG4aWgNadMYvK/vOSkRRBXXuIog0gMp2R4GA14YW2n3 Xz8FPgk2EqMQ2837aID89+UK53nVZHwpScSK3Orc5nDEzLn/bR5njKbMfAjO7UOamqoWREViKwx OhAfx/FBw9TR4A0E81z7kOWFMCOAkXp85KZ6JdH35agYcOPqpGlPRHDQDxn2TdyBkHsOXQaEYPs ISoFbZwYB3YgY6Bg4iyijIiH6h0xwwNiNP7ZXuRLqz4KKParHrKHwtE1XrMepcrfjrXO4zTaTPF mhmhDOCMUAKkfp7ZzPJ8zeCjBuRudoUSO6HjueaHM+CHzXyGawM+lotF7w== X-Received: by 2002:a05:6102:4194:b0:7b4:9eba:69dd with SMTP id ada2fe7eead31-7d0543c880dmr909387137.45.1793287284941; Thu, 29 Oct 2026 08:21:24 -0700 (PDT) Received: from localhost ([144.121.52.163]) by smtp.gmail.com with ESMTPSA id a1e0cc1a2514c-991f74e5e22sm206563241.4.2026.10.29.08.21.20 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 29 Oct 2026 08:21:23 -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 v5 02/11] libcamera: Document ScalerCrop in physical pixel-array coordinates Date: Thu, 29 Oct 2026 11:20:49 -0400 Message-ID: <20261029152058.896209-3-halfline@gmail.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261029152058.896209-1-halfline@gmail.com> References: <20261029152058.896209-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 documents ScalerCrop and ScalerCropMaximum in physical pixel-array coordinates. The existing descriptions refer to the active pixel array, while pipelines use native sensor pixels relative to PixelArrayArea. 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(-) 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 15:20:50 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ray Strode X-Patchwork-Id: 28706 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 31042C324C for ; Thu, 29 Oct 2026 15:21:38 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id E6AD5690BE; Thu, 29 Oct 2026 16:21:37 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b="wNGo3ZnA"; dkim-atps=neutral Received: from mail-ua1-x92b.google.com (mail-ua1-x92b.google.com [IPv6:2607:f8b0:4864:20::92b]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 5BA36690A0 for ; Thu, 29 Oct 2026 16:21:35 +0100 (CET) Received: by mail-ua1-x92b.google.com with SMTP id a1e0cc1a2514c-990835cec4bso1375537241.1 for ; Thu, 29 Oct 2026 08:21:35 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20260707; t=1793287294; x=1793892094; 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=qkB8CTmZtsW7GOSMvr1zjF6q2XWsZmXzbAJ8b54gKkg=; b=wNGo3ZnAgZBe8mCTdz48tEJ+4Q5GxdUX9IisbMPTFaNGCzfbWarg76Z4m9hayCigNX 0yTU3tpo9P39IDdkehf28JqUWIr10nAK5SN/rqPLY0n2u7QKAce7avjeGlUQyKHL81mp 1VAn3us1DUMUbDZunvIQeuwdUDkfosrKJb10/xOq5nTO1fl750xcM2mlxQkLeq8NOhO2 89crd0FxS+FrYrvrSAq2Gjk2IeboMe5ax2DhVZJCpzkRWflMDHv/THDqQEKBIeOCLild p0BjQWYJCgTppWsFJubZJ6Hnl8PgQ66WWqRu/t3bRbqsN+Yg95L4GpcRbVF6kpFBQ/rJ cT+g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1793287294; x=1793892094; 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=qkB8CTmZtsW7GOSMvr1zjF6q2XWsZmXzbAJ8b54gKkg=; b=CjZeG/gGhOhW+gbZcITUK9VsRl/p9pz+RpkoYwamAOzlBmLU6f5BKP9Ax2SRAWii+t qcGe2RVi5RMEItxMMCaWehAzqGUaaBv+vL6YeAYiAjNOsF+pNPmpwnRPPf3UY7mJRT8K sEhpGc8SoZ5E2yFVZgMme5owPJJLyYkdtc/su941Ml+PG5JQMPjf+AYHE9jtX08cVtup TndzHDTerbcoueMmGAsNAx0a5/VCk2ze6c1BewoUp+YgT7IomUubOaPkct5f6apJ3F7l vQbwf0qkV4CV2lRf71JXXfjoQK4UjkO9H1S/0V+e1vF9mP9lxDT1z1hBbj2sDpa2+aSk XBIA== X-Gm-Message-State: AFq9FYLD9w9XparOUx10nzZw3+Y0y4Bmd34A/0aTEYlxDfIOb2KANp+O agAV4DV/Kh+zRhlAXiznJ2dCQFTGCfWvncw2gSYUP+LyfiU0OMKV1CbxOUh1a7fugdI= X-Gm-Gg: AYBFou2koDGOQ9u+P1fTrrLs0W6EoVyJiTwvC227N76Xmh5fxhUOOCDu8+smgiSZ71n pL96qZhP9xf2nCGYUAZAprqNwUw/bcxPm6ngVBmjYXRL7A7tz1I1DZmw+ReSkmV6hEyCEMQwb9w KOUDtnmDeaCXYrv3X4zTD+zu4/azbIdL0hQIYsLjUgxwme6SHz0V6H0KzzYXZXaziuFAON8Us1T rNhertg8FM20bUCusR2jMg9qylwGUfLlU9EK0JHUssizlbICGE5sM9raC4xixQ5oAX4HWyMp8ae 2DLyYObYu+Xvc7KPZKtxOliVk/qpYR2+kAX3cCdnwBcgQUYgT+2FgRzLZPljGeTiK1zXrAecAKf vwND/StRBOXW7u2YQQD8RPCXZyZnSj0kTq0WuGmdQqCE1ZuKWHyEQrjPyt+G4tL6fDpXXRe1g24 52Mw5M5f0YPGVP8IP5qqSwboFRdcMGT+gnGzDLYo4M2WHfCPylbpyAcHG9Wrl4R/US7KUEwQx18 nFmFAOrOPJ6lcVtLzJ0NDI28F7YqyCJS135m+gmZ8ePGqwwnVLUUtLvW+flINGUWmU+6g0s/L1y Al6mSGum0IN9K1d5AC/PuPpYCZ96QUGZ+aI1ek6KX9SP+QftRCXcF6LK X-Received: by 2002:a05:6102:5106:b0:7cc:115:ddf4 with SMTP id ada2fe7eead31-7d03a4e928cmr3022712137.25.1793287293731; Thu, 29 Oct 2026 08:21:33 -0700 (PDT) Received: from localhost ([144.121.52.163]) by smtp.gmail.com with ESMTPSA id a1e0cc1a2514c-991f74e5e22sm206563241.4.2026.10.29.08.21.25 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 29 Oct 2026 08:21:28 -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 v5 03/11] libcamera: v4l2_subdevice: Return optional selection rectangles Date: Thu, 29 Oct 2026 11:20:50 -0400 Message-ID: <20261029152058.896209-4-halfline@gmail.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261029152058.896209-1-halfline@gmail.com> References: <20261029152058.896209-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 distinguishes an unavailable selection rectangle from an ioctl failure by returning the rectangle through std::optional. The query returns 0 and an empty optional for EINVAL or ENOTTY from G_SELECTION and propagates other ioctl errors. The output is cleared on entry so a missing rectangle or failed query cannot leave a previous result behind. Invalid pads and nonzero streams on devices without streams support are rejected before the ioctl. The cached capabilities are initialized so the check is also safe before open(). EINVAL from the driver still means only that no rectangle was reported for the current configuration. Callers handle the empty result explicitly. Raw sensors still require their selection rectangles. Legacy sensors retain defaults for unavailable selections and propagate query errors during driver validation and sensorInfo(), so failed reads cannot produce default geometry. Signed-off-by: Ray Strode --- include/libcamera/internal/v4l2_subdevice.h | 5 ++- src/libcamera/sensor/camera_sensor_legacy.cpp | 42 ++++++++++++++----- src/libcamera/sensor/camera_sensor_raw.cpp | 31 ++++++++------ src/libcamera/v4l2_subdevice.cpp | 40 +++++++++++++----- 4 files changed, 82 insertions(+), 36 deletions(-) diff --git a/include/libcamera/internal/v4l2_subdevice.h b/include/libcamera/internal/v4l2_subdevice.h index c37a82af..f0818d6d 100644 --- a/include/libcamera/internal/v4l2_subdevice.h +++ b/include/libcamera/internal/v4l2_subdevice.h @@ -96,62 +96,63 @@ public: unsigned int pad; unsigned int stream; }; struct Route { Route() : flags(0) { } Route(const Stream &snk, const Stream &src, uint32_t f) : sink(snk), source(src), flags(f) { } Stream sink; Stream source; uint32_t flags; }; using Routing = std::vector; explicit V4L2Subdevice(const MediaEntity *entity); ~V4L2Subdevice(); int open(); const MediaEntity *entity() const { return entity_; } int getSelection(const Stream &stream, unsigned int target, - Rectangle *rect); - int getSelection(unsigned int pad, unsigned int target, Rectangle *rect) + std::optional *rect); + int getSelection(unsigned int pad, unsigned int target, + std::optional *rect) { return getSelection({ pad, 0 }, target, rect); } int setSelection(const Stream &stream, unsigned int target, Rectangle *rect); int setSelection(unsigned int pad, unsigned int target, Rectangle *rect) { return setSelection({ pad, 0 }, target, rect); } Formats formats(const Stream &stream); Formats formats(unsigned int pad) { return formats({ pad, 0 }); } int getFormat(const Stream &stream, V4L2SubdeviceFormat *format, Whence whence = ActiveFormat); int getFormat(unsigned int pad, V4L2SubdeviceFormat *format, Whence whence = ActiveFormat) { return getFormat({ pad, 0 }, format, whence); } int setFormat(const Stream &stream, V4L2SubdeviceFormat *format, Whence whence = ActiveFormat); int setFormat(unsigned int pad, V4L2SubdeviceFormat *format, Whence whence = ActiveFormat) { return setFormat({ pad, 0 }, format, whence); } diff --git a/src/libcamera/sensor/camera_sensor_legacy.cpp b/src/libcamera/sensor/camera_sensor_legacy.cpp index 792e4b09..570372a3 100644 --- a/src/libcamera/sensor/camera_sensor_legacy.cpp +++ b/src/libcamera/sensor/camera_sensor_legacy.cpp @@ -1,44 +1,45 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * Copyright (C) 2019, Google Inc. * * A V4L2-backed camera sensor */ #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/bayer_format.h" #include "libcamera/internal/camera_lens.h" #include "libcamera/internal/camera_sensor.h" #include "libcamera/internal/camera_sensor_properties.h" #include "libcamera/internal/formats.h" #include "libcamera/internal/media_device.h" #include "libcamera/internal/sysfs.h" #include "libcamera/internal/v4l2_subdevice.h" namespace libcamera { class BayerFormat; @@ -363,103 +364,117 @@ int CameraSensorLegacy::validateSensorDriver() } /* Verify if sensor supports horizontal/vertical flips. */ auto queryFlip = [&](uint32_t id, const char *name) -> std::pair { const struct v4l2_query_ext_ctrl *flipInfo = subdev_->controlInfo(id); if (!flipInfo || flipInfo->flags & V4L2_CTRL_FLAG_READ_ONLY) { LOG(CameraSensor, Debug) << "Camera sensor does not support " << name << " flip"; return { false, false }; } return { true, flipInfo->flags & V4L2_CTRL_FLAG_MODIFY_LAYOUT }; }; auto hflip = queryFlip(V4L2_CID_HFLIP, "horizontal"); auto vflip = queryFlip(V4L2_CID_VFLIP, "vertical"); if (hflip.first && vflip.first && (hflip.second != vflip.second)) { LOG(CameraSensor, Error) << "Flips with differing alter bayer order properties" << " are unsupported. Disabling flipping."; } else { supportHFlips_ = hflip.first; supportVFlips_ = vflip.first; flipsAlterBayerOrder_ = hflip.second || vflip.second; } /* * Make sure the required selection targets are supported. * - * Failures in reading any of the targets are not deemed to be fatal, + * Unavailable selection targets are not deemed to be fatal, * but some properties and features, like constructing a * IPACameraSensorInfo for the IPA module, won't be supported. * * \todo Make support for selection targets mandatory as soon as all * test platforms have been updated. */ - int ret = subdev_->getSelection(pad_, V4L2_SEL_TGT_NATIVE_SIZE, &pixelArrayArea_); - if (ret) { + std::optional rect; + int ret = subdev_->getSelection(pad_, V4L2_SEL_TGT_NATIVE_SIZE, &rect); + if (ret) + return ret; + if (!rect) { /* * Default the pixel array size to the largest size supported * by the sensor. The sizes_ vector is sorted in ascending * order, the largest size is thus the last element. */ pixelArrayArea_ = Rectangle(sizes_.back()); LOG(CameraSensor, Warning) << "The PixelArrayArea property has been defaulted to " << pixelArrayArea_; err = -EINVAL; + } else { + pixelArrayArea_ = *rect; } - ret = subdev_->getSelection(pad_, V4L2_SEL_TGT_CROP_BOUNDS, &readableArea_); - if (ret) { + ret = subdev_->getSelection(pad_, V4L2_SEL_TGT_CROP_BOUNDS, &rect); + if (ret) + return ret; + if (!rect) { readableArea_ = pixelArrayArea_; LOG(CameraSensor, Warning) << "The PixelArrayReadableArea property has been defaulted to " << readableArea_; err = -EINVAL; + } else { + readableArea_ = *rect; } - ret = subdev_->getSelection(pad_, V4L2_SEL_TGT_CROP_DEFAULT, &activeArea_); - if (ret) { + ret = subdev_->getSelection(pad_, V4L2_SEL_TGT_CROP_DEFAULT, &rect); + if (ret) + return ret; + if (!rect) { activeArea_ = readableArea_; LOG(CameraSensor, Warning) << "The PixelArrayActiveAreas property has been defaulted to " << activeArea_; err = -EINVAL; + } else { + activeArea_ = *rect; } - Rectangle rect; ret = subdev_->getSelection(pad_, V4L2_SEL_TGT_CROP, &rect); - if (ret) { + if (ret) + return ret; + if (!rect) { LOG(CameraSensor, Warning) << "Failed to retrieve the sensor crop rectangle"; err = -EINVAL; } if (err) { LOG(CameraSensor, Warning) << "The sensor kernel driver needs to be fixed"; LOG(CameraSensor, Warning) << "See Documentation/sensor_driver_requirements.rst in the libcamera sources for more information"; } if (!bayerFormat_) return 0; /* * For raw sensors, make sure the sensor driver supports the controls * required by the CameraSensor class. */ static constexpr uint32_t mandatoryControls[] = { V4L2_CID_ANALOGUE_GAIN, V4L2_CID_EXPOSURE, V4L2_CID_HBLANK, V4L2_CID_PIXEL_RATE, V4L2_CID_VBLANK, }; err = 0; for (uint32_t ctrl : mandatoryControls) { if (!controls.count(ctrl)) { @@ -871,65 +886,70 @@ int CameraSensorLegacy::applyConfiguration(const SensorConfiguration &config, /* \todo Handle AnalogCrop. Most sensors do not support set_selection */ /* \todo Handle scaling in the digital domain. */ return 0; } V4L2Subdevice::Stream CameraSensorLegacy::imageStream() const { return { pad_, 0 }; } int CameraSensorLegacy::sensorInfo(IPACameraSensorInfo *info) const { if (!bayerFormat_) return -EINVAL; info->model = model(); info->pixelArrayArea = pixelArrayArea_; /* * The active area is a static property, while the crop rectangle needs * to be re-read as it depends on the sensor configuration. */ info->activeArea = activeArea_; /* * \todo Support for retrieving the crop rectangle is scheduled to * become mandatory. For the time being use the default value if it has * been initialized at sensor driver validation time. */ - int ret = subdev_->getSelection(pad_, V4L2_SEL_TGT_CROP, &info->analogCrop); - if (ret) { + std::optional crop; + int ret = subdev_->getSelection(pad_, V4L2_SEL_TGT_CROP, &crop); + if (ret) + return ret; + if (!crop) { info->analogCrop = activeArea_; LOG(CameraSensor, Warning) << "The analogue crop rectangle has been defaulted to the active area size"; + } else { + info->analogCrop = *crop; } /* The bit depth and image size depend on the currently applied format. */ V4L2SubdeviceFormat format{}; ret = subdev_->getFormat(pad_, &format); if (ret) return ret; info->bitsPerPixel = MediaBusFormatInfo::info(format.code).bitsPerPixel; info->outputSize = format.size; std::optional cfa = properties_.get(properties::draft::ColorFilterArrangement); info->cfaPattern = cfa ? *cfa : properties::draft::ColorFilterArrangementRGB; /* * Retrieve the pixel rate, line length and minimum/maximum frame * duration through V4L2 controls. Support for the V4L2_CID_PIXEL_RATE, * V4L2_CID_HBLANK and V4L2_CID_VBLANK controls is mandatory. */ static constexpr uint32_t cids[] = { V4L2_CID_PIXEL_RATE, V4L2_CID_HBLANK, V4L2_CID_VBLANK, }; ControlList ctrls = subdev_->getControls(cids); if (ctrls.empty()) { LOG(CameraSensor, Error) << "Failed to retrieve camera info controls"; return -EINVAL; } diff --git a/src/libcamera/sensor/camera_sensor_raw.cpp b/src/libcamera/sensor/camera_sensor_raw.cpp index 55b386ce..641801a1 100644 --- a/src/libcamera/sensor/camera_sensor_raw.cpp +++ b/src/libcamera/sensor/camera_sensor_raw.cpp @@ -397,92 +397,93 @@ std::optional CameraSensorRaw::init() /* Drop non-raw formats (in case we have a hybrid sensor). */ const MediaBusFormatInfo &info = MediaBusFormatInfo::info(code); if (info.colourEncoding != PixelFormatInfo::ColourEncodingRAW) continue; mbusCodes_.push_back(code); std::transform(ranges.begin(), ranges.end(), std::back_inserter(sizes_), [](const SizeRange &range) { return range.max; }); } if (mbusCodes_.empty()) { LOG(CameraSensor, Debug) << "No raw image formats found"; return { 0 }; } std::sort(mbusCodes_.begin(), mbusCodes_.end()); std::sort(sizes_.begin(), sizes_.end()); /* * Remove duplicate sizes. There are no duplicate media bus codes as * they are the keys in the formats map. */ auto last = std::unique(sizes_.begin(), sizes_.end()); sizes_.erase(last, sizes_.end()); /* * 3. Query selection rectangles. Retrieve properties, and verify that * all the expected selection rectangles are supported. */ - Rectangle rect; + std::optional rect; ret = subdev_->getSelection(streams_.image.sink, V4L2_SEL_TGT_CROP_BOUNDS, &rect); - if (ret) { + if (ret || !rect) { LOG(CameraSensor, Error) << "No pixel array crop bounds"; - return { ret }; + return { ret ? ret : -EINVAL }; } /* * \todo Implement querying the physical sensor size based on pad 1, * stream 0. See * https://lore.kernel.org/linux-media/20260409201501.975242-23-sakari.ailus@linux.intel.com/ */ LOG(CameraSensor, Warning) << "PixelArrayArea might be incorrect"; pixelArrayArea_ = Rectangle(0, 0, - rect.x + rect.width, - rect.y + rect.height); + rect->x + rect->width, + rect->y + rect->height); - readableArea_ = rect; + readableArea_ = *rect; ret = subdev_->getSelection(streams_.image.sink, V4L2_SEL_TGT_CROP_DEFAULT, - &activeArea_); - if (ret) { + &rect); + if (ret || !rect) { LOG(CameraSensor, Error) << "No pixel array crop default"; - return { ret }; + return { ret ? ret : -EINVAL }; } + activeArea_ = *rect; ret = subdev_->getSelection(streams_.image.sink, V4L2_SEL_TGT_CROP, &rect); - if (ret) { + if (ret || !rect) { LOG(CameraSensor, Error) << "No pixel array crop rectangle"; - return { ret }; + return { ret ? ret : -EINVAL }; } /* * 4. Verify that all required controls are present. */ const ControlIdMap &controls = subdev_->controls().idmap(); static constexpr uint32_t mandatoryControls[] = { V4L2_CID_ANALOGUE_GAIN, V4L2_CID_CAMERA_ORIENTATION, V4L2_CID_EXPOSURE, V4L2_CID_HBLANK, V4L2_CID_PIXEL_RATE, V4L2_CID_VBLANK, }; ret = 0; for (uint32_t ctrl : mandatoryControls) { if (!controls.count(ctrl)) { LOG(CameraSensor, Error) << "Mandatory V4L2 control " << utils::hex(ctrl) << " not available"; ret = -EINVAL; } } if (ret) { LOG(CameraSensor, Error) @@ -987,64 +988,70 @@ int CameraSensorRaw::setEmbeddedDataEnabled(bool enable) * source stream, as there would be something seriously wrong * otherwise. */ bool enabled = false; for (const V4L2Subdevice::Route &route : routing) { if (route.source != streams_.edata->source) continue; enabled = route.flags & V4L2_SUBDEV_ROUTE_FL_ACTIVE; break; } if (enabled != enable) return enabled ? -EISCONN : -ENOSTR; return 0; } int CameraSensorRaw::sensorInfo(IPACameraSensorInfo *info) const { info->model = model(); info->pixelArrayArea = pixelArrayArea_; /* * The active area is a static property, while the crop rectangle needs * to be re-read as it depends on the sensor configuration. */ info->activeArea = activeArea_; + std::optional crop; int ret = subdev_->getSelection(streams_.image.sink, V4L2_SEL_TGT_CROP, - &info->analogCrop); + &crop); if (ret) return ret; + if (!crop) { + LOG(CameraSensor, Error) << "No pixel array crop rectangle"; + return -EINVAL; + } + info->analogCrop = *crop; /* The bit depth and image size depend on the currently applied format. */ V4L2SubdeviceFormat format{}; ret = subdev_->getFormat(streams_.image.source, &format); if (ret) return ret; info->bitsPerPixel = MediaBusFormatInfo::info(format.code).bitsPerPixel; info->outputSize = format.size; std::optional cfa = properties_.get(properties::draft::ColorFilterArrangement); info->cfaPattern = cfa ? *cfa : properties::draft::ColorFilterArrangementRGB; /* * Retrieve the pixel rate, line length and minimum/maximum frame * duration through V4L2 controls. Support for the V4L2_CID_PIXEL_RATE, * V4L2_CID_HBLANK and V4L2_CID_VBLANK controls is mandatory. */ static constexpr uint32_t cids[] = { V4L2_CID_PIXEL_RATE, V4L2_CID_HBLANK, V4L2_CID_VBLANK, }; ControlList ctrls = subdev_->getControls(cids); if (ctrls.empty()) { LOG(CameraSensor, Error) << "Failed to retrieve camera info controls"; return -EINVAL; } diff --git a/src/libcamera/v4l2_subdevice.cpp b/src/libcamera/v4l2_subdevice.cpp index a69de154..0ab9d9ff 100644 --- a/src/libcamera/v4l2_subdevice.cpp +++ b/src/libcamera/v4l2_subdevice.cpp @@ -1082,163 +1082,181 @@ std::ostream &operator<<(std::ostream &out, const V4L2Subdevice::Route &route) /** * \typedef V4L2Subdevice::Routing * \brief V4L2 subdevice routing table * * This class stores a subdevice routing table as a vector of routes. */ /** * \brief Insert a text representation of a V4L2Subdevice::Routing into an * output stream * \param[in] out The output stream * \param[in] routing The V4L2Subdevice::Routing * \return The output stream \a out */ std::ostream &operator<<(std::ostream &out, const V4L2Subdevice::Routing &routing) { for (const auto &[i, route] : utils::enumerate(routing)) { out << "[" << i << "] " << route; if (i != routing.size() - 1) out << ", "; } return out; } /** * \brief Create a V4L2 subdevice from a MediaEntity using its device node * path */ V4L2Subdevice::V4L2Subdevice(const MediaEntity *entity) - : V4L2Device(entity->deviceNode()), entity_(entity) + : V4L2Device(entity->deviceNode()), entity_(entity), caps_{} { } V4L2Subdevice::~V4L2Subdevice() { close(); } /** * \brief Open a V4L2 subdevice * \return 0 on success or a negative error code otherwise */ int V4L2Subdevice::open() { int ret = V4L2Device::open(O_RDWR); if (ret) return ret; /* * Try to query the subdev capabilities. The VIDIOC_SUBDEV_QUERYCAP API * was introduced in kernel v5.8, ENOTTY errors must be ignored to * support older kernels. */ caps_ = {}; ret = ioctl(VIDIOC_SUBDEV_QUERYCAP, &caps_); if (ret < 0 && errno != ENOTTY) { ret = -errno; LOG(V4L2, Error) << "Unable to query capabilities: " << strerror(-ret); return ret; } /* If the subdev supports streams, enable the streams API. */ if (caps_.hasStreams()) { struct v4l2_subdev_client_capability clientCaps{}; clientCaps.capabilities = V4L2_SUBDEV_CLIENT_CAP_STREAMS; ret = ioctl(VIDIOC_SUBDEV_S_CLIENT_CAP, &clientCaps); if (ret < 0) { ret = -errno; LOG(V4L2, Error) << "Unable to set client capabilities: " << strerror(-ret); return ret; } } return 0; } /** * \fn V4L2Subdevice::entity() * \brief Retrieve the media entity associated with the subdevice * \return The subdevice's associated media entity. */ /** * \brief Get selection rectangle \a rect for \a target * \param[in] stream The stream the rectangle is retrieved from * \param[in] target The selection target defined by the V4L2_SEL_TGT_* flags - * \param[out] rect The retrieved selection rectangle + * \param[out] rect The retrieved selection rectangle, if available + * + * If the ioctl returns EINVAL or ENOTTY, no rectangle is reported and \a rect + * is empty. EINVAL can indicate an unsupported target or unavailable stream + * state; an empty result does not distinguish these cases. + * + * Invalid pad indices and nonzero streams on devices without streams support + * are rejected with -EINVAL before issuing the ioctl. All other ioctl errors + * are returned unchanged. The output \a rect is cleared on entry. * * \todo Define a V4L2SelectionTarget enum for the selection target * - * \return 0 on success or a negative error code otherwise + * \return 0 if the rectangle was retrieved or is unavailable, or a negative + * error code otherwise */ int V4L2Subdevice::getSelection(const Stream &stream, unsigned int target, - Rectangle *rect) + std::optional *rect) { + rect->reset(); + + if (stream.pad >= entity_->pads().size() || + (stream.stream && !caps_.hasStreams())) { + LOG(V4L2, Error) << "Invalid selection stream " << stream; + return -EINVAL; + } + struct v4l2_subdev_selection sel = {}; sel.which = V4L2_SUBDEV_FORMAT_ACTIVE; sel.pad = stream.pad; sel.stream = stream.stream; sel.target = target; sel.flags = 0; int ret = ioctl(VIDIOC_SUBDEV_G_SELECTION, &sel); if (ret < 0) { + if (ret == -EINVAL || ret == -ENOTTY) + return 0; + LOG(V4L2, Error) << "Unable to get rectangle " << target << " on pad " << stream << ": " << strerror(-ret); return ret; } - rect->x = sel.r.left; - rect->y = sel.r.top; - rect->width = sel.r.width; - rect->height = sel.r.height; + *rect = Rectangle(sel.r.left, sel.r.top, sel.r.width, sel.r.height); return 0; } /** * \fn V4L2Subdevice::getSelection(unsigned int pad, unsigned int target, - * Rectangle *rect) + * std::optional *rect) * \brief Get selection rectangle \a rect for \a target * \param[in] pad The 0-indexed pad number the rectangle is retrieved from * \param[in] target The selection target defined by the V4L2_SEL_TGT_* flags - * \param[out] rect The retrieved selection rectangle + * \param[out] rect The retrieved selection rectangle, if available * - * \return 0 on success or a negative error code otherwise + * \return 0 if the rectangle was retrieved or is unavailable, or a negative + * error code otherwise */ /** * \brief Set selection rectangle \a rect for \a target * \param[in] stream The stream the rectangle is to be applied to * \param[in] target The selection target defined by the V4L2_SEL_TGT_* flags * \param[inout] rect The selection rectangle to be applied * * \todo Define a V4L2SelectionTarget enum for the selection target * * \return 0 on success or a negative error code otherwise */ int V4L2Subdevice::setSelection(const Stream &stream, unsigned int target, Rectangle *rect) { struct v4l2_subdev_selection sel = {}; sel.which = V4L2_SUBDEV_FORMAT_ACTIVE; sel.pad = stream.pad; sel.stream = stream.stream; sel.target = target; sel.flags = 0; sel.r.left = rect->x; sel.r.top = rect->y; sel.r.width = rect->width; sel.r.height = rect->height; int ret = ioctl(VIDIOC_SUBDEV_S_SELECTION, &sel); if (ret < 0) { From patchwork Thu Oct 29 15:20:51 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ray Strode X-Patchwork-Id: 28707 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 E6F39C324C for ; Thu, 29 Oct 2026 15:21:47 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 9C300690D3; Thu, 29 Oct 2026 16:21:47 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b="TjHoEutW"; dkim-atps=neutral Received: from mail-ua1-x92d.google.com (mail-ua1-x92d.google.com [IPv6:2607:f8b0:4864:20::92d]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 1DA64690A0 for ; Thu, 29 Oct 2026 16:21:46 +0100 (CET) Received: by mail-ua1-x92d.google.com with SMTP id a1e0cc1a2514c-9912c47367cso654715241.1 for ; Thu, 29 Oct 2026 08:21:46 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20260707; t=1793287305; x=1793892105; 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=TjHoEutWbYsuhuvHu5vUc1O+uuVOFaTpvv8esTdCcMoMKTy/EloutBTE2k1w1KNzzi +5ZEo8ot9PXcXIdWyYEO7rQmCjt1c7jySh1nodcYjKfSKUy5c03+1LuqMJqhRetJFt7z ns8P/6LtO9PonmED3c5qViwrwy9whQyMglzmnkJ1j3F8Ecnf94pCvylqo14G3y0stHaZ 3T+hM4AGDaJDypvdPJyFKSEkQdkIrPSUgS/CgEcXJCg77iAz3Xv90e/uA8DpX0FnywRN ZJJUcz9mzaVqN8mZaeRbD5JvbB0sqe2ig3FYWrsUt6/qkqiesdMBmTh0Mb5Db6fGrQyQ QbQA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1793287305; x=1793892105; 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=mCjUTe48hnHU+Xb7DbINFEOvIclwcrExgY1yLxecLCqceJZSquA7inpqVpGRiL/dgn FpH6wTkjbd0QvyqueebXzNWR7cJoScHkvGusKqRCC8LcVcKtIPuZ/RDr5QQa/cdfUzjo kqIzSuMF6odzZtcgXBBPBuRvC7i7WIPvA/t5UkUXH5Ce7Bi6qkLemlDE6P5jO4cs8s0A CgNSKkSIqUwMgwJNxtZhzaLqM6QgbhIr4phj4sPGjz7GRmza6d38GlUwMqNmKwOB0tzg Dggx2Fba+cou/1fjj25bTuNnm1mhK2OM1Dda1i7bw0sOGZEz2SsZl/GJDmdKn55QYZhy 2qCA== X-Gm-Message-State: AFq9FYJGlxEU4sEc4ZBm6+PorYETKYAmh6IAEeDb50tNrqI50QcMT8Uj Slg0/iZfdqbl0f3/oCv83d0Uu33hRDaH5J5IlHZc+LZN8wKfVxysFRNKskG2k7ZuyXU= X-Gm-Gg: AYBFou3Gu9WH+28np5VhCV5Ul1LYVaQv5jTHAjYD1bxVHcMMfwYUgaAbRA+dTBFgW8k 4EtkvDidyT0oNWDhGuk51Vw+QLuSsTHUNARoQrZEN2VGIrJ8ej8LRkP2HtdyHy5N7GGTDEsg9tz F8XN3SU7gkKHsRYSYAG9uj21Ww16Yxx8tFVTL2bSsqKKdTPhe72bwtrxC9ojsMd4LHKR+BPSmqc nA5JDMMrY49YWNASirxMyUelFEs8j1yxh9cCBoU+Hh36V3DzFALDLD2CTGbK8OsQANDL/Jt3Ikw HwS4zlh11I4wzonTRXNXNd0nk8KipDOc1pSfb9+KwMrY42SQSaKCNYg4/CL96/QLNxolTmEvRVN GHTfg7C/QLha6FP+hgmlkCam5MkmXePSFOrKrmIpTg/t+G4ST9RuZ5vTQPHCyaXaqKKPdpNWzXd ijiqM4Utsj1IxWjnDJ4eCEuEFOq2RHJgoU+ahsZLj3ZV+hft7lNJPBLCTMYEuNI2YyX6EzqcR+t vT65l9UbH1iSSp5zPyVdGRaN0wKgOIm2/2qTevc4NdVJSlnGPlRTdiDJjmgbontuyUAx22FvzA/ RqByaMgqYQ/i3EXc4ASGk8KvJ7/nC3TWYijABbN9N0kaSSsjNP4BwXv1 X-Received: by 2002:a05:6102:f0f:b0:7cc:ace:24ad with SMTP id ada2fe7eead31-7d053b5df5dmr994055137.0.1793287303816; Thu, 29 Oct 2026 08:21:43 -0700 (PDT) Received: from localhost ([144.121.52.163]) by smtp.gmail.com with ESMTPSA id a1e0cc1a2514c-991f74e5e22sm206563241.4.2026.10.29.08.21.34 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 29 Oct 2026 08:21:38 -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 v5 04/11] software-isp: Align statistics with the visible sensor crop Date: Thu, 29 Oct 2026 11:20:51 -0400 Message-ID: <20261029152058.896209-5-halfline@gmail.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261029152058.896209-1-halfline@gmail.com> References: <20261029152058.896209-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 15:20:52 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ray Strode X-Patchwork-Id: 28708 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 DCF4BC324C for ; Thu, 29 Oct 2026 15:21:52 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 8B4C6690B0; Thu, 29 Oct 2026 16:21:52 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b="g+876WpO"; dkim-atps=neutral Received: from mail-vs1-xe29.google.com (mail-vs1-xe29.google.com [IPv6:2607:f8b0:4864:20::e29]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 9373A690A0 for ; Thu, 29 Oct 2026 16:21:50 +0100 (CET) Received: by mail-vs1-xe29.google.com with SMTP id ada2fe7eead31-7cc1cf48a39so852340137.1 for ; Thu, 29 Oct 2026 08:21:50 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20260707; t=1793287309; x=1793892109; 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=g+876WpOXouVxArXeRHARUvwbKZHUb16iM4uX4dlpzyDVJPNeSItCSDXCeI9rjkgSU eGP1etqmJ5JVVMU/+47iTvhmlLlTq/e+St7WeVmC2MLjEcz+nMBE4CtXZG9VSV0fyIG/ yUizyrgSlGIvY8LrSN7PEDsu0CvS5NXrVXyDKBTEwPtqWJzMi2/xHsMWP1KVBkBo40no EHe5PryoUpvkjo9CXFFYcI6s7GI7a83eMezHI+WddzAoMCFZlXDZHySK6lwV+uAxlzGU 2VziyghHSr3wvMjDcSBAt1HaQpiei0P36irvVuO8KGdVLiB9VsyzHfCHOehyK+D01cUE +yTg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1793287309; x=1793892109; 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=2WkUCY16t8RXGMHJmZd0jpjOtgt2nNHYCL5Vof8laWOa9PcHo5BkCGmR89AqPuaz+e 5GKAIB5uIpPyiUOIEe5brHPE8ZllrKVb6jcsDUJXdUIZzAV4CIkTSCR8JK9W1Tc1+OEG XkSayboHJW/Jmrjv1TZumvyxdnjpLIQbuo2FUrsn8K+hmTTTrxpTaw0M/v3s5fgpgZ5u WgsIXuuNxgYyCsUsEqgAUwPYcoNsOw6I3T6Fvuixv2KP4kDvDpzGiBlOKA7faF725cqY Zr2rnCRlP5yDBumOTAobY6iFzYIqR7xefH50sl84Hy8zY32de4yslVfoekaetrp16VwZ 1ssw== X-Gm-Message-State: AFq9FYLWTjRMlIpI9MST0UY9mqL5YFQP1xA2vWZZ86dj8kpA3fyhJhEm BimNVWmSFYXg6G2MsL37LG9HPSuA3SbvT+heYrVHrU4sAxrmQDxV2AiFTwMBXqBBh1s= X-Gm-Gg: AYBFou1ro2RdswPFqX6L6ID/Z2Cup6HILJRZ/jxqJ2TCcz1u1apGTswtl/tERwkaHmg 9wXh0NYrU2YOq+jYn+JJzEX7SODCtHt2+yWgTSVf+mUulR1HZb2AkygZhHz/dTZDN7om7l/v8EV iIkBg4ta/r24qvbcUwP56ng6lW5tIh6TpjwLR1J1abSdIjcRuVQUkLHP8WG+0cFhURC8TDIihAl pVqo1UE68h84oBiFVtdmrg8lUgAFixpQb6oEwoIuo+taumiyTu4tdVmQ+2o5pkSgf7rklBI9wzM 7Gn4OprAygwBkD+6mIHG3NLmHLDIzeBCFUP0ApieVaJQwkOJCPM1IJSOpaiJxIxKUbiliWHT2Tx r50jw9gDsM5Nx/qyDHzom5kxES0ot+2u/d/gnk0LGo3E+IUEEXg0z901+WYmOMOD1ZLScJhT2aG ZJXPkrEHnO4LtMLuR6JZSA71HbzTI6CYH17OtZ98LufSQI2AcGf5/ZLe9CuITiGbjqlJbMOSS2w l4DT6GuOTgh39ZrZKs9B7LdhdsVES2AqO5aMqE6eqUTIDyeQrzAb0WYtebOIP9eSZYV2/xwUyXZ kaSCDo0Odxi0VzKB2udoFLaKTk9PeQn20cGPxnAZkElinFptel/F25lU6ddYSRxHcXzeOIahq+e o7g== X-Received: by 2002:a05:6102:2b9c:b0:7b5:4700:3a6a with SMTP id ada2fe7eead31-7d03a5bd56emr2570905137.26.1793287309311; Thu, 29 Oct 2026 08:21:49 -0700 (PDT) Received: from localhost ([144.121.52.163]) by smtp.gmail.com with ESMTPSA id a1e0cc1a2514c-991f74e5e22sm206563241.4.2026.10.29.08.21.44 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 29 Oct 2026 08:21:47 -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 v5 05/11] tests: Validate the visible sensor crop Date: Thu, 29 Oct 2026 11:20:52 -0400 Message-ID: <20261029152058.896209-6-halfline@gmail.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261029152058.896209-1-halfline@gmail.com> References: <20261029152058.896209-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 15:20:53 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ray Strode X-Patchwork-Id: 28709 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 D700CC324C for ; Thu, 29 Oct 2026 15:21:56 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 94A0B690D0; Thu, 29 Oct 2026 16:21:56 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b="beYjyKPs"; 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 AA5BB690A0 for ; Thu, 29 Oct 2026 16:21:54 +0100 (CET) Received: by mail-vs1-xe2b.google.com with SMTP id ada2fe7eead31-7bec5112765so1252803137.1 for ; Thu, 29 Oct 2026 08:21:54 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20260707; t=1793287313; x=1793892113; 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=beYjyKPsiwDUy6KvoIqDkcQ3EndubiyqBnppGvAvipnN0ACtp11y3/QMD90JyfSXka KNsM/qZXLHPXA17OZyXkO4LOAGhfvgb9hyU2SPqnJXgwYgSAtJjOrcGuzi+Cr/dWEf/1 bi6VlqVWDAPSXIv7Ap8IuYrWBLw75LWPnNQa6ZfUEdKHN8DKBE3XFP7UKzkel4chLmlH cvKuw+sd4SD28LW+MIz1R00oqVG3KKA8bcwJSalXPwvwuvyoC3oN0ozX/oYzXq4m1+IV BBHy4oJJn+I52eK2Cba23PAP+U5TJx0S6B0Ig/7oylXGIPPm0PEw8XZdwbFmiX+3NNaJ zWHA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1793287313; x=1793892113; 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=JzdlSeIZT+f0cvTnP/tuOwIpcjSpX/mJfWiE4zcc4rl2DsiXLSE6EaFBPFsx5zG/KC qx0xNsPcWjoon7+WdriTVZTiIv6/KRPOhNgnLIcrKGiV7AiuXg8X8p1DeLV63MDxsuCP XuQfIyBZu6+k6mWXIVEDjjwlSX1atfbEe31NFRdQ4N5RMRXw0A/z8azFoLI+nJn+KX+I o93Jxbm9GXBiuiPefpDNmXH2ZaVnpe72AkJWbpl/fYdjw8IEzpTrNIMdYC83R05NiCCN b3bWtkGlxtxkSUoGU6dnp/DgcS50DpGo8xv0o1fZVOZHyFXwTuqhq8cN/xsiTcT+RnsH rz5Q== X-Gm-Message-State: AFq9FYLMu5He4IpYBOsUt9I4+YA+BoTxQbQpGF3lr7VoTU31v0VVTYbH rWNlZFijmBOXtHc4vXgONJXc6fxNMRAxq+xJnlcj+3LEH229qvR9IqUNIH/rkIvSgnE= X-Gm-Gg: AYBFou2s9BGtzUt927szBb7EVQAG9Nc84RToDwfMv+44y3uxL2PrpO0t0dpzCDijjYA 0626xp5jPFVdaCMM+DsFUeI6L8ZODgiYMGgHtD/yMw2s+MtBYTILNIsy+d5tvrM7Ns45x/2n9Xe z2Ws4UXfAOE5ezq517NVWgDmDFkhTmikmRxuPxVhwCYt8l+OP48jxGil6y7yt3SuWdQ3uVWVZIn o9tNVx2TZ+ax/ti6E3FNDml5ZYvgVxZDXTBS6XhQWZfpV9IkpP9mY8e+NcKfFQOPyvwv9rfp2uW MX5K8h7cKzZd90ycT1PiW/y8XbSCPgDTOqNUib9Z+WocFIzWhbHwtPI2m1rgHxDh49MNFqMftrD 7lZSY6zMpRWzA9IJXEIA4W06+KHappPJYkLI7E8s5IWLL1Sxj1S5hz4IQbIuTwdggXGjaeieYyL o/vgAYvwhaFlmpRwvFocJwcwS/4ACP2FZOw4W/6CP/ORKqS3eQbS/whTeNo3Mgn7XN5QMovwknN MMemYdUYEJ3OpzBhVJ2u1JOKio1RWQW4DZxMY3mW3tw0E4wSUyblHHVbTIbxPwxEwvh9M4EFDOP BiQ3hloQEIX9QAtdklGfT2OQJCjDARXEpBDFCJygMUFWSNbjof8TxTCp X-Received: by 2002:a05:6102:3c83:b0:7c3:7286:c291 with SMTP id ada2fe7eead31-7d03a4846c0mr2739269137.19.1793287313433; Thu, 29 Oct 2026 08:21:53 -0700 (PDT) Received: from localhost ([144.121.52.163]) by smtp.gmail.com with ESMTPSA id a1e0cc1a2514c-991f74e5e22sm206563241.4.2026.10.29.08.21.49 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 29 Oct 2026 08:21:52 -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 v5 06/11] software-isp: Expose the configured image crop Date: Thu, 29 Oct 2026 11:20:53 -0400 Message-ID: <20261029152058.896209-7-halfline@gmail.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261029152058.896209-1-halfline@gmail.com> References: <20261029152058.896209-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 15:20:54 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ray Strode X-Patchwork-Id: 28710 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 ACABFC324C for ; Thu, 29 Oct 2026 15:21:59 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 651B0690CD; Thu, 29 Oct 2026 16:21:59 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b="p3Puh2nB"; dkim-atps=neutral Received: from mail-vs1-xe29.google.com (mail-vs1-xe29.google.com [IPv6:2607:f8b0:4864:20::e29]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 4F3DF690A0 for ; Thu, 29 Oct 2026 16:21:58 +0100 (CET) Received: by mail-vs1-xe29.google.com with SMTP id ada2fe7eead31-7cc53938a8fso1414325137.2 for ; Thu, 29 Oct 2026 08:21:58 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20260707; t=1793287317; x=1793892117; 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=p3Puh2nBWTMmKGAlqXaae+zqFbmGA29XBpRP0JuJK7ryneuNv3RgkicOe6Beao1Ya2 RJ04U9f/e13884MRLRcI5qUB7dvePVyZ6IKEPYNH25RyBzmu81Qv5p1scKJSSITOmOd3 /VGkiGBbkxr/xh5o+4HezA2vbnaBdDlMECciCdYXwT1BbU5bz9ml2oMssIb70Oa2Ud0/ jAEXJNk9RXc9OUnPpdk4yrO2ff735eN675XKgF75yUh3xRkdUY1YOeo6hpxZrfoKiTFG EIe3optw/H+RINJNuny2U9DXhCx9siayCHjrZfnvnEXGaCqiZbgAgxrCjZH37InqRe4Z mvWw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1793287317; x=1793892117; 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=Phs2Pq+4eYWzUv3BQn8iTkfw9q4VacKtUXjjDcnLb5jAPyeAsauBu+42FSmKGA0ZzZ wtp+yHK7oN9dQ/fPQe9RJ0F8HY7GTHXA+X1YSOK4C/uXNe4b6qF9i5Pk6ZvrpgN+HxbX bAXpL3jYSUVqSLhniKpilpsd5rCVO9k+nEguk75WaVSGb34HLvXpfqhzRhr22soOHBtW qITX5GcfhvKc78TKhYdLsZ4oKtTlUQSVKlrDrrpk8ud0tZmQiGhq9EwEoZQ37l0M/QRE ch6ZDI9cyUEDQnBFPGHsujKlHQjLjmMx5aD70MC40+oguy2EBq4U1tsVWxCtVmO4uGw8 ywNw== X-Gm-Message-State: AFq9FYJVF0O6qLtxkghbCZi7LdxahnCoI4cP6Iwqs6zR9WY8RtDfgkwb AcNe4No7LLtH814NM5nePYcYQMOLHBDaHwgZzWTn+yNX80XVfz4ieg2GMp+wcjUGrac= X-Gm-Gg: AYBFou2P4JHdwTe2bHvjDH0Irmr9CSLRi+/WY3iscNhXhI8OWWaLdAFjoOJ/d2KMkW5 Ry/dMGVay2UDvq7Wwr19bOEh4fiKGXCxl8mTpysMJWwhjZeuLD2glfH0Mc76Z3maSfuAEk/VDJx Pmikch64vek50+dBiENzgAd7gR3aYPFEKJ1R+Onm06Uhjs1D7nz/wu1vla99u7N+4rQYFDRTPjF 7cXXsrv56EXg0+1u1okpuBMIp52Qszzh5YWhOAXuzdTAguhWz8zpxlTyZZ0KgNOul/TIeK7NRxi S2xFItfzKBqRpIWDtUHVaXWSduC4532Ays1TpS5EsB/MJ9HnWJ6QP+sOlRrNfWiVUEWpSR97WeG 0P/A0bVpncsEQLeCIalVl456gjWx82Uspx0DvnQfKZkAF70Mbams+PU7bYXZf59fpq/BY9DhusF j8xoQHkSQuJyLC/Qnbf+c9n/OWqlit73nenvfrL0GDCHWFzbu6CyiVv+Nzzn0gjMqOGOL1eEt/g vEv6LPnoHvklmARU7bWbgRqZYBCVwTTubKmF2+DD9d6jFUh5XWUDLXK3n5VIWxLeELyvl8cMNRI rhSGSfQ/3rL8wkfmY6I2xQSgPQQ0P4Up8d/b0YUrO8EEfx/BL6Ya2z2b X-Received: by 2002:a05:6102:6043:b0:7cb:bc5e:7d9f with SMTP id ada2fe7eead31-7d03a417b55mr2675409137.11.1793287316992; Thu, 29 Oct 2026 08:21:56 -0700 (PDT) Received: from localhost ([144.121.52.163]) by smtp.gmail.com with ESMTPSA id a1e0cc1a2514c-991f74e5e22sm206563241.4.2026.10.29.08.21.53 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 29 Oct 2026 08:21:56 -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 v5 07/11] tests: Check the configured CPU image crop Date: Thu, 29 Oct 2026 11:20:54 -0400 Message-ID: <20261029152058.896209-8-halfline@gmail.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261029152058.896209-1-halfline@gmail.com> References: <20261029152058.896209-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 15:20:55 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ray Strode X-Patchwork-Id: 28711 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 5BBEFC324C for ; Thu, 29 Oct 2026 15:22:04 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 05A16690DF; Thu, 29 Oct 2026 16:22:04 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b="ISJWkngm"; dkim-atps=neutral Received: from mail-ua1-x935.google.com (mail-ua1-x935.google.com [IPv6:2607:f8b0:4864:20::935]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 9820A690A0 for ; Thu, 29 Oct 2026 16:22:01 +0100 (CET) Received: by mail-ua1-x935.google.com with SMTP id a1e0cc1a2514c-980c4258105so1210104241.2 for ; Thu, 29 Oct 2026 08:22:01 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20260707; t=1793287320; x=1793892120; 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=ISJWkngmhhV/+xPdlw/bvT8PYFfTmTAXxN4+bdcgN8AAYcNF5K+T4+5h5jGKQQdX4n EUXainRLo+KODYaQvwPG+mjsnpiBOZ5mpkrKoPaOHdbReil4wnJxLU9YEXu8jkUkZBTL 2ENlsyPV7rgkCwk8liSjWzOc00qXe5dWxHyCHUZxoAisl/ZRBnedNebeM7Fzx89T596s xpuYtJojDMGVfQ6n3sqKklLoPXuFxzcddu5iExn+NSKeV+RMk9s66/ttaf6kwo9l2Lkr maYmv296B41VLwxQY7UnIMs2/Sz5hqmWa5IfJXroSkdCD2xB+Y1+K/6ZALgm82NSlPR8 bQDg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1793287320; x=1793892120; 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=hzD6kEhtI59qxC19recqk3Zta8P98Nr8zgfCEAcgNi5tEUviQcVwCPUiuVW66ew/6+ ntqqLM0PWL1W+j21OMEYZ7OiqRjZbbnA5jakYGRf8XLMtIv6Mgnawc+n/atMsuXnpUAM c6DWqc+P16MCi7RBbjoCDyVeTJLFGe0lr0eMj9qRds4i62CMm9dcKKgIo8e+C11QN3sP dmkzxNbCgIQ4ad/j223Xk79lGT0rGeM8fyjbsi2FChxH6vu6JbfEtmdYZKgyojKXlTwX tdBHozj+Q+DXTls+gBWuVIavoxEDI/Q1Uxy0re4vI9eUPZfKQyyfuDoWzupA0y/tXbV8 wJmQ== X-Gm-Message-State: AFq9FYIhgqIB3jvuu2RGA2frRhYHDMew0OYDBLHvYF5C4dJdkKuAP4sH ShGerQHEZs7YebPvAAzp3gc3INdpI0AZsuIoXW44cdsdD1hKC9XUtIhV/oaWPj7VWdE= X-Gm-Gg: AYBFou36DIiIISV77PFCawzZOGRw4bpOAI/pavssyVaNm0qlsdZrdjjWJALCQ5l5jIr SlFY26mjrovR4/Vgv53jDjIHe0bw9j/pp4wNiDsCw3AYcIVSVJvReq6PxJ17LTw0kO9PgogF9fU tuKQUHwX0dcH1oXDt9GkPsgLG6si2pAcU1ibp+iNEAFrHld7gv7IJvyHA3cdROcMXVcvRgjt3ld lGnuLDBQW0OKLiNHzVu4oYQqBjm0cKJgJkU7DQ+ZWblf9CK7UC+98WPOqTqK3T/YGDQy1d39aRs s9XtBI7FF+jchU/tDfIsjGSAqE2v+0sJ9TXP2amFBEBH8+M1IoZPpqpRUUV7zQXoo8Q098e0OoZ 5WlEtxMW3dGjGYusUEJothZpOMOa0rPfuOhEwywLkVROoCFeW7YHU6vZ/+1DVhTBRvFsIUPhPb7 YOdil3q4HS/gRTJLN4F9cOjVZT/wTgt9lSgKfqutSQnH4RViZmtE+fMr0CwaCm498pbY0p96hMh fGfbN227jdCMalpqxpFkRGxLTvzah47wfBChsQkmbSfo26hyUK7pXaSVz4bE1VmX6R6zX2dnYRS kpLFXtzfLDJPgKJ5LzcdHL8Md+ReaGqwrEhSJSYPzpW7orFx+Xd2Qxju X-Received: by 2002:a05:6102:f9c:b0:7cb:fd69:4155 with SMTP id ada2fe7eead31-7d03a1f60afmr2933017137.5.1793287320490; Thu, 29 Oct 2026 08:22:00 -0700 (PDT) Received: from localhost ([144.121.52.163]) by smtp.gmail.com with ESMTPSA id a1e0cc1a2514c-991f74e5e22sm206563241.4.2026.10.29.08.21.57 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 29 Oct 2026 08:22:00 -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 v5 08/11] software-isp: Map input crops to native sensor coordinates Date: Thu, 29 Oct 2026 11:20:55 -0400 Message-ID: <20261029152058.896209-9-halfline@gmail.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261029152058.896209-1-halfline@gmail.com> References: <20261029152058.896209-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 15:20:56 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ray Strode X-Patchwork-Id: 28712 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 7E9B4C3261 for ; Thu, 29 Oct 2026 15:22:07 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 28287690DE; Thu, 29 Oct 2026 16:22:07 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b="r8ZmC/37"; dkim-atps=neutral Received: from mail-ua1-x930.google.com (mail-ua1-x930.google.com [IPv6:2607:f8b0:4864:20::930]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 6F337690A0 for ; Thu, 29 Oct 2026 16:22:05 +0100 (CET) Received: by mail-ua1-x930.google.com with SMTP id a1e0cc1a2514c-9908c849564so666724241.3 for ; Thu, 29 Oct 2026 08:22:05 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20260707; t=1793287324; x=1793892124; 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=r8ZmC/37FHbv2iJlD3xfrfTLOzTx5mkrOoqKdvKXOv5HDuxX13oW1VhqZmjsRgdEBD Q/xWR8fhBslKahqSCcWcfApwJCp0vV4eChLkgD7TW8Nf8IJ6BL/WV4WI8vp21GTSBDLv E6x4sqIEkP2WlYBgV94Gwb8cmbaUph2nzsYve4lExtVEeY/H9d8iMgIZWmAiHPKs/iTG fL+J712TmWdB6AXDXEdOK3tNjpj9CgWuFn0YCB4W2kt8do5twWOZWknNQgGXijwmz5XW RFXLfioiPf9Tlzc0h97EA/xOcZLtpJtvUGRazyAufSJEnN22QNUKZdLgbKP/3rfboIoi AjLQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1793287324; x=1793892124; 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=VXFKSlyYQOgCIHSeBQCx5bYFBzatQC7EiJ0fWGi817zT/Y+slkVel+2j9mTdhbNwTf 70NOaU6DI1nKJEsJh5xkTTpLsjWwq5MIGUuu3u9hmMTOBImW8zUTwj9uuqrtPNsQ4GKN 9vMhNhySM0YJ+8It2XAKDD5R5q2l6Zm8/u8v/RDZEUt3r6PZJl6vdhJeN3bjq381vXtN iv3Ev8W9X2+1YSaMszTiAGcPlw4UKHAuHUNI8j9DE9vSDYfcZiHEJDV2qRJJn9SS1T8V PqGDrqDNxdima/L6QUxFE7qYuhR4PtFQbvXOEoB3CYkJyA+F3MpcBVXADCp8VtIUEAzZ vNbQ== X-Gm-Message-State: AFq9FYKwoLexqSARBqVFJvDjR6/sXfcBLL+hNpDya9qQdwRjkjHC+7Tt eTNgFkWRY9q9mbaCITe5Qb++EVR+xgmUIKwIaKlkr6tVYuoEjTOpsF87cO8P7Vv/eNg= X-Gm-Gg: AYBFou1n/M2rf7ARd69QgAFOyjr6l2fqmA9fxd/CUUo7uCtWD1hmlXY9NYJRuFNYZyS GNko11+OfXGiRAhNV/AWRIlhpKJf32P+tVUh9CFSs8YcRmk3TtY+ZYapxd+kfjHMVEm5iQN1WDc xRfYUzef7tgev59tBUNqjv8K2p6BSZdtqkCcmYqM3LfUndb+c6/TBkFipcKlt1QAp7Z3DMpwlcG KVXJG/buqi9c9fjgKZ9rwOkI0qh91Halp5hMCTDG95wf24X4M4lS0knoSaPTtCXrav2QXBqUVOQ gP4i9s0b2ior8ksU5texN12oh7gEMsAd76tlXy5Pl+b8S5T2yOfKbdoStnf8SHnyrsPtQV7VxIO 6SWrRf3yfivEmgvVG7krYHMUkQG/wDzx4Y3MjKAmBRXemXCBjDhYXnSOZnSRPDVbboduKgUuO4n GQvfluaH+n/Ogq0NSiw4RbOtLS5tKKTcc24gUQ4+pdPKWOD7maPQFv2nGy5VwlR5r+RXiuZIJMC /PySxLTPdUx/djYuGMAppP0Armf7D+CGpMpgO9fbSvmFkJr6hQYFi6ACFehWilfr1hjK/46LE3y FYFSx9grbeMED6qQAfdec8jBHuUJQrZ35E0xVSzBVK4enKmy1zBlSrgV X-Received: by 2002:a05:6102:2b9c:b0:7cc:1192:563c with SMTP id ada2fe7eead31-7d03a5d6b85mr2513305137.25.1793287324207; Thu, 29 Oct 2026 08:22:04 -0700 (PDT) Received: from localhost ([144.121.52.163]) by smtp.gmail.com with ESMTPSA id a1e0cc1a2514c-991f74e5e22sm206563241.4.2026.10.29.08.22.00 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 29 Oct 2026 08:22:03 -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 v5 09/11] tests: Validate native sensor crop coordinates Date: Thu, 29 Oct 2026 11:20:56 -0400 Message-ID: <20261029152058.896209-10-halfline@gmail.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261029152058.896209-1-halfline@gmail.com> References: <20261029152058.896209-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 15:28: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: 28713 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 EE6EBC324C for ; Thu, 29 Oct 2026 15:28:45 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id AD7E9690BF; Thu, 29 Oct 2026 16:28:44 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b="yeKMouho"; dkim-atps=neutral Received: from mail-ua1-x931.google.com (mail-ua1-x931.google.com [IPv6:2607:f8b0:4864:20::931]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 12511690A0 for ; Thu, 29 Oct 2026 16:28:43 +0100 (CET) Received: by mail-ua1-x931.google.com with SMTP id a1e0cc1a2514c-9908346255fso467868241.1 for ; Thu, 29 Oct 2026 08:28:42 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20260707; t=1793287722; x=1793892522; 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=Hx/FivPDLWAe81HIfDI1aU06X5jwYbGdLCMlToRwdJY=; b=yeKMouholhqIcvNiAw0djtWvt9sKTQsK96qY+ZLHyOJyd5Yn6GaWdJ7nn+daW+L3CN 2CiaIrQxRYWVwqSKyOJyTBDtBVttHIGcsYEUiGugfZwkuIYz1rBIjYcp/kqfWSbPdGCv oKOtWFSylmWTPOP8/Okcp2LmKhnnnenQERx0XpI+EclVVol8BPmVf2Mxp9D/9yNr27S3 ebjVFgbKVFkOgBJeNgjlsEu1EeZ509TkyrJZHE0SbfSKKQrZF3DQBIeR/GfDdzKdKFjH //JyqFuaAFfunuh9TUtxKI9JXif9ERwbG9x+TEDswt+b+4TQLRn/f2+NP0Xxvt5rOPBD 1lFA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1793287722; x=1793892522; 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=Hx/FivPDLWAe81HIfDI1aU06X5jwYbGdLCMlToRwdJY=; b=HlCDIAG2aJoNwYj9zGclvFhngjDEqx+ia6PLBh/zWbP5Xetxc+Uquw4CCbhEq5ZZc2 B39Axl8bLKm/xtUVfexxgtmfswyWe3sjpsfqSHbBi6O7I6aLWtzB2KOpMFzZtZfIX0PI +vtDyEGaZ1NYn6VDLvM3Jg4DXHWwDcqCuOB8Q9iyRIOjr4M9Twb0zsagz9W1/sh7Yh6M AwNYRqLgT0G772eIQVRqXWG2hr3RnAGbHT+dNMmJ92riUx/ljso39jMHfLYDIldpDix6 4xhMLbzhqJEUM0guZWuTQT4f9W5OsXY9Pw4ybkSTdd7aPTjJ/V8FmAQrvfscn+tpfbwg E7nA== X-Gm-Message-State: AFq9FYI5EGs3QaIjtzQHR3n507BNYxPRJ8XkYHn08qcl2U6vwsgnASpJ ddMAJwUUZVfd73Bx8xWri599TNKdEd6NEPh4G56C8RbAyJcABPvrNwKecK47wmlPW0g= X-Gm-Gg: AYBFou02Uo2Z1Mxp87TLfAKVxARgjxo2EmN74ILFOfXp1aoav8a3KRNKLvTrMQlxQ6E p4GpLosKW/p/oBy7E9xPIQTYO3yc/+2RsUW8hZ5qUWE43Yi7CVXmAbyw9WOZZe/vzEtP3WgEVkL HXZL1m8KlBIUg4/1nW7DONplo7DhIXESIKjG+Ix7M/4S/aZNhqoKoUEXq22paEF96W+MrSzq7iO LnKqBwFex6EYP0ONPCZ0k6P9A7MvpvgHiPONa9xY4Lo9WQuiq6afyYzO1TGqjGpAiWCAT4rDPw3 jn+5vWxfKUkYMzaF8clHomI7L3tx1p70LimG5fMYnvrzW75F8k9iaWNmuBrMWQGR9euqAB0j+ke lr2COnH5WoI+LrCok4e16QwtscwdNuqcat4AVKMUjYRHxdjTmcE6oC1IfdM/OdhuHWEGK8Q5JUw szaCPDALSchd/jQZ+fJ0SOu43CvEJvsXxSSQVhMEG9Lp+G5HQkaDIC2l+fmHRNyLfZDGtz7p6/Q QU4neIsQPhlNjqW/BNDSqxmwU8Ea2IXG80A7i1rITPY8WhMpQwRG73IZNZK9LRHWZgq0XaWs2du /ObL9EFQjwdGJJceYum5OkBByRXyKOhAT0j2L0300tXCngJWxEYVHr5KrQ== X-Received: by 2002:a05:6102:e0b:b0:7cc:a4b:b0e3 with SMTP id ada2fe7eead31-7d0541814a2mr949960137.13.1793287720594; Thu, 29 Oct 2026 08:28:40 -0700 (PDT) Received: from localhost ([144.121.52.163]) by smtp.gmail.com with ESMTPSA id a1e0cc1a2514c-991f76aea18sm182670241.11.2026.10.29.08.28.37 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 29 Oct 2026 08:28:39 -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 v5 10/11] pipeline: simple: Report the software ISP's maximum crop Date: Thu, 29 Oct 2026 11:28:25 -0400 Message-ID: <20261029152058.896209-11-halfline@gmail.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261029152058.896209-1-halfline@gmail.com> References: <20261029152058.896209-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. The mapping requires 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. ScalerCropMaximum is updated only after configuration succeeds and is removed 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 15:28: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: 28714 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 D086CC324C for ; Thu, 29 Oct 2026 15:28:49 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 7EFE1690D7; Thu, 29 Oct 2026 16:28:49 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b="zNsVz3GS"; dkim-atps=neutral Received: from mail-vs1-xe33.google.com (mail-vs1-xe33.google.com [IPv6:2607:f8b0:4864:20::e33]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 50D45690BB for ; Thu, 29 Oct 2026 16:28:46 +0100 (CET) Received: by mail-vs1-xe33.google.com with SMTP id ada2fe7eead31-7cbbb7b90efso714844137.2 for ; Thu, 29 Oct 2026 08:28:46 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20260707; t=1793287725; x=1793892525; 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=hW2Xt9W+E2y0B/M4nuixvNoENM4hU13DMLc/usEW5Vg=; b=zNsVz3GSB7BODRBlgPxCZGmIloXAl+1DXDuWiny8dbBh6jMNwMd9FLol4JQd6LFIpQ +r+kM36hYUSxlP1m+F3w16eF7QnkQ1MVBIZLMO+eHea81FekpSeRjKezvjGWNPmLCrC+ HA5uGeWCBvMSHM4AeiXJq9roskRMF0OanP1azKYkpjgMPllE6KBPjiqfUK9nynu9OMEX VX7yxYUfHavKmbzc+68ozci/e+iZ3nVFo08xLtTKAoryYXrx7StKQnTwgVdFQ4/VTe55 uWhmS2Nj1vA09KwEAne0bxo3VW9OvZD02uufM0Bit9iPogfLjGx2wdY5/CLF8YE8Zuim 0GSg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1793287725; x=1793892525; 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=hW2Xt9W+E2y0B/M4nuixvNoENM4hU13DMLc/usEW5Vg=; b=Pl65NXzizaoshlNFy5D5CWYhfaYoxZwqnQivUPfBEkgPwZIjsX6IAesjxMgUnu3jm3 ie22lAyfpLjZ/ultsnY2Htpz7SyewSpMyu//DqxedO3uK+gRdZKxX3JGvUOe+Z9j03e0 TVWe5oCFU/A6Fj0/1merLj+ewbj+aGDG3j2y3SFj6hn0ZMTuKUeor3eBnHW8OSjxBJvG fSXQw24vnqZodjh3CFKNeaBBfOLt3QY9wCgrINeNwvf56x/kfCUt/aczMAnuzGS4KIyb 96dnY2+sFj0v8sArli1OptKKoA/ld0T9HKeJYdPGsWycwb4qGD2TE8ObWH5XLeRX8EOM 1+dg== X-Gm-Message-State: AFq9FYKY8h6iNREZvhYzws4qNZ0091ImCHqOomt6lo7PKc/p8p5yw36W 6axBiZsfQXdGhGWi1VS+eEMT9AmAGKQ5w0cmeoHO/bNEcBcS1OxesBfE/WfqvY/Z2pI= X-Gm-Gg: AYBFou2ogmrmVVfX7YTdTIrg2ae+XLwKY+keaCCHUCL3O4eYgByxEPZC6jCHRKqMnHv gtJkOBzN67Dvv86ZeTdDyJXQeyLXrkRyb2Ynv2z4ZX5VeHxdqGTqEh5JP1f+oEk35ydo8dZ2wl1 rrMDlJ2gsw9D+zqZRmdFKYP0Pkmc8IOu0eDI//o2oa89pjB+OfqXh1ZvY1I7+dwGgVUNjqLwJe8 E0PJdcSqGQzi0u1ti49QbUVuPrageh2Z+rZ1pq5AGY3QWBsIrF1/bPigh9kGmVA468joqaDAcXA z10H6h4/+p0Wj4S+DAigYJ6d0+P+pwNvceNZCOW/+uS9VqTm3bNUHvvm32pzgGsZpsrGC52sCGc bEcl6H5QZ2suraK1XElZ8EDn8NHhokpzz0H/gTK+9QuAOgIfV/hce0Z6H2ykWwgKGqn83+7vtNE 3EL/GrFzisGl830rqi+8aBqeI2u/V/H8yOIXkzT6T3HjNz5QYGlEEdU01Qwfw8mlWgEBLdyZ8Lr bU+Veaf4Mqw5XrXvAbJQqOvZlbWG75lCP0KjhWV4ENnNmAvOi4ypWnky9XXLto6vqWZtFfJEW0t uI+Nzsprg/7kpu0F92OJNgLikERAbzdxJZuI/GwrsmsIZ9sSoAj9Olay X-Received: by 2002:a05:6102:4422:b0:7cc:9d47:9c9 with SMTP id ada2fe7eead31-7d03a6567eemr2620649137.35.1793287724959; Thu, 29 Oct 2026 08:28:44 -0700 (PDT) Received: from localhost ([144.121.52.163]) by smtp.gmail.com with ESMTPSA id a1e0cc1a2514c-991f76aea18sm182670241.11.2026.10.29.08.28.41 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 29 Oct 2026 08:28:44 -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 v5 11/11] pipeline: simple: Include the software ISP crop in request metadata Date: Thu, 29 Oct 2026 11:28:26 -0400 Message-ID: <20261029152058.896209-12-halfline@gmail.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261029152058.896209-1-halfline@gmail.com> References: <20261029152058.896209-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 the 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,