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 */