From patchwork Thu Oct 15 19:43:32 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ray Strode X-Patchwork-Id: 28462 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 0A890C3316 for ; Thu, 15 Oct 2026 19:49:48 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id CAEAF68E17; Thu, 15 Oct 2026 21:49:45 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b="aZw1q5bn"; dkim-atps=neutral Received: from mail-qv1-xf2a.google.com (mail-qv1-xf2a.google.com [IPv6:2607:f8b0:4864:20::f2a]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id C84BF68E06 for ; Thu, 15 Oct 2026 21:43:47 +0200 (CEST) Received: by mail-qv1-xf2a.google.com with SMTP id 6a1803df08f44-9178aabd30dso56357056d6.1 for ; Thu, 15 Oct 2026 12:43:47 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1792093427; x=1792698227; darn=lists.libcamera.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:from:to:cc:subject:date:message-id :reply-to:content-type; bh=ebe8FD7l513a+k0oPXIiPYPmPSSM8yZTgjPaSBvBQac=; b=aZw1q5bnfx3UOQJq09BDo1NbjlrviaOMSuu8oKwSMY1jE3UlmIO/9FtjHIWmwZ+y3z oKYNrjbVHOGI3ntrpG2XATnblml8L8KJ5jZGeL5bz5ObBDtpNrmZqIntE8sGpjZMt8SQ T+WpFLiaEc3Fxt2wV2i8XVieeVNDiwAUw4RsqRFFLElD3TQpbkeiMDW2rJah3fiQiBDm sQO4EZntIGKgFuhViR8527BqOsXBD4G9Q3S8u8ih8W5zCHd8QJ5IdlXNYf/w37s5X3G5 XlHfuAAvp0hFEHyYBCaAMtnrxdHVDAZSFEG4BPznJRFpAtPFKekqcLJBPplYHRjp4ShY MqgA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1792093427; x=1792698227; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:x-gm-gg:x-gm-message-state:from:to :cc:subject:date:message-id:reply-to:content-type; bh=ebe8FD7l513a+k0oPXIiPYPmPSSM8yZTgjPaSBvBQac=; b=cHfT3WkD3UEpm/nMm/B9GYqJXOpKh+DXZmTpLR5G/RbJTexbWebFR+zly4mR42KtoE sYvTh6OBmKWTfBzkgdZfk/6hAaqfwgN7lQUHXaeKKfn2k8YCWBV3YbxVL7t4r1HODe5D Coovja2jTIfrhKS7nn/eWqSUkdv0hTUdAIVgLLJNvM8Q3bDf83E/5NBiEekmpL3DUdWj ylTw0r+C1s2oF06yXdj4zdq/Ld+XjqKh/mSTMn12UJg/J3X0PzgIPptFCHepVPPnQRqq UoToWbvESMW7Gjyunzc22qJyyiTHwZ7sBC4C1JN6KYn743Neh/8VxWqZW7v6cKaJM6cB GgKw== X-Gm-Message-State: AFq9FYIgvTb2hBwmjOCBxv6d4EPmEwTy8EHgqNAhT9lfW6GpQQsEPP29 fGdPYsTJz6+RQi04bIJmGlBgk0oUeGPrkAklsYgLBbRoyM8by5iKXpcCz9z25e5gsN4= X-Gm-Gg: AYBFou302c72mzLIDNrdBJHZAd8nl8dRxLvwqcZhjJA/Wul9tdSIHd3Pm6rjCn6vSnf I389IAJM5c45A9VSsX60VRjl+naYO7GEJJx5FPrgHCkVwe6inaKsTu3l7IDU87kfCI6DR/fI7Bs e6+y/6/kENz0+SpjSBd47YpQXyQOxya37cwkPfzUzLFKrRcLnvltexmPKC7B9lrHoODB7mrpaj1 0F1ETN/5R3KtTnMpp5uKLnzAR4RG/MCzabTrXXAJG59AXQwRC99PPd2JRB5SH9dvlYpRL5OqaSY TgE4IMfkFDl6ZJB1A2yIVyCSADdS0Sv/j0DZWJvkiGrxRDF84v+aLRgM0vh3cRBkTKbmdqoCjeF FEtCiOtM7isrzMpL5HwuFNFZRFG66am0NfVq9gPY6/5BAPO473C5F8fouIRgDTRb77hzxCjzSva bEHtrrYQgYQrNv/6yW5duL57JLfqnu+lMPrdnlA9DqvAn7s8+CHQZb+ByOG2eS9kCPUZq/2Zkn/ QVwcI6beZIUvRsxoh8je4GjzxevULGVSqGr X-Received: by 2002:a05:620a:440e:b0:93e:9834:3863 with SMTP id af79cd13be357-93ebd22a0d3mr3296902385a.15.1792093426498; Thu, 15 Oct 2026 12:43:46 -0700 (PDT) Received: from localhost ([144.121.52.163]) by smtp.gmail.com with ESMTPSA id af79cd13be357-93f1a18176esm15370385a.20.2026.10.15.12.43.45 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 15 Oct 2026 12:43:46 -0700 (PDT) From: Ray Strode To: libcamera-devel@lists.libcamera.org Subject: [PATCH 1/4] software-isp: Center the GPU image crop Date: Thu, 15 Oct 2026 15:43:32 -0400 Message-ID: <20261015194335.287960-2-halfline@gmail.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261015194335.287960-1-halfline@gmail.com> References: <20261015194335.287960-1-halfline@gmail.com> MIME-Version: 1.0 X-Mailman-Approved-At: Thu, 15 Oct 2026 21:49:41 +0200 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" From: Ray Strode The GPU debayer path scales the image to fill a requested output size and crops any excess image area. Its projection positions the scaled image against one side of the output, so aspect-ratio changes and Bayer border removal shift the visible scene. This commit centers the projection on the output region in sensor coordinates, distributing the crop across both sides. Signed-off-by: Ray Strode --- src/libcamera/software_isp/debayer_egl.cpp | 23 +++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/libcamera/software_isp/debayer_egl.cpp b/src/libcamera/software_isp/debayer_egl.cpp index 300822f9..0f515c5e 100644 --- a/src/libcamera/software_isp/debayer_egl.cpp +++ b/src/libcamera/software_isp/debayer_egl.cpp @@ -364,70 +364,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 */