From patchwork Fri Oct 16 20:28:09 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ray Strode X-Patchwork-Id: 28474 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 103E6C3305 for ; Fri, 16 Oct 2026 20:28:39 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 2A85668E3B; Fri, 16 Oct 2026 22:28:34 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b="HGYsFgaf"; dkim-atps=neutral Received: from mail-vk1-xa35.google.com (mail-vk1-xa35.google.com [IPv6:2607:f8b0:4864:20::a35]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id A47AD68E37 for ; Fri, 16 Oct 2026 22:28:31 +0200 (CEST) Received: by mail-vk1-xa35.google.com with SMTP id 71dfb90a1353d-5ea969fafeeso141510e0c.0 for ; Fri, 16 Oct 2026 13:28:31 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1792182510; x=1792787310; 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=BFkFlnHOd5d01Z6C9QKgQSdXmevfTQGxyuVigzB3iag=; b=HGYsFgafiJg50Vy7OfKwEWMRo0na4606BLia5+vGkeL1BXrs5HfhKOUgnnM7nJRj9t +yx0w8/6zU47RRM6B2QWlsdZDJQk2shsG83jWWvRcaFf4GeDgv7CgZxOjwSKaFfPhI3C +0o2oXPnL+PTCrWfdqoX+VAKaSJ2DQbA4wdgilHsMHDZgUhgcdAABmcqvf72j4gpQLO2 JPheWmnmDLfdt+nnLOLz+ysJuQ22oWS5rj1noGUM9BahsUTJbwjFuM/0Mi6b6MKaKbEX lKpQrgHEX8YjxqOI26FqACWle/aVN38dR6MvocBy38AAa8UAy+yIgHCflYUQh0G2IcJE ZEuw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1792182510; x=1792787310; 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=BFkFlnHOd5d01Z6C9QKgQSdXmevfTQGxyuVigzB3iag=; b=VVdljqo4AljAxiWNX94Xhkw4VHEoDfNdvlbxHhpHqY5kacOo/VwKPbanlvXLIGVeCv Mt7kjrNmAxMYEjouBgqE1tXmm5gUMu+5JajQNNT7fdIjcKgL6mMYW316koX6ZimJcwmA +Nxj+3YU/S75IzCr3pZYXQjbemQGk7NaiT/6vr9XfB4KzgxKStDPFeHGTX8/1Ig413Kz 0IuE77eJ2CQ57r3RGGxz2xfZElB6FPl028rVSCrq1rvzJVQuY57OO4qssWgZ7tKEx4EA 9GvLUKhErXdHFUWbxYMmIRI+WJtiq1M9HNf5IyKSvzIqMHBJNJoZ7WR4lCqQ6YrMVOjO Jx8Q== X-Gm-Message-State: AFq9FYI1Q6e6PgnCN+uTLsS+iqP+Fb6nsPv2vajofOf3Fm94exvIbOnN yAoerWoavn5WlZrgPYZGd7Yw2FlhwN3weD5dTFkcddOCMowyFLnET+sr/WLVa8IZx9A= X-Gm-Gg: AYBFou2TOokskfz+Z3vOnEVuV6bvQAy8zElCgsj3wdeBYpqqH//Sl360b6Uc6aIshHi roHLd5jRXJiJTvL+fUwFMGYi4sLL3qgYDl9MlUAuJRJQ/r19+pJ0iuWqYUcNk4ftsZPbkG1kk5y 4mX7T6LcBV4pkhakX5MIyGxbo7uZDNODYlRt5vdSJkzjcha7/td1UTDMyYJkSuOR6qUsSEbOK6a Egl5PIHSEg9NC8FnKWxA9ziW9tjCQ866eHTFI3ln4yR2qJa2PaDNAl8E+z1Nbwv/x02veefwr0l cHsOEqSgmiAw8PC1NdifdFIeny9NqbQz4iqtT0zUJwjaqtlbFzs5ABxLpKvxL8YVmBYoDmnAP72 +/4gd/OJnh221MZiNnvN+x8le20VfkiCrj+zU/WpB1Fs+/Tn6QOIQ1TiccIZe7IeeBHgZszjNis NQ0VtuV2OL02mvArvXQFpNQaqy5ZrnIhJMklKfi45gpEVHeamKZeBa+tSftFGjtg37wZyuvmxm8 ei/RDbttxJHcCWk3+MFh65299KQ X-Received: by 2002:a05:6122:4b0a:b0:5c9:a60b:e5d8 with SMTP id 71dfb90a1353d-5eaaa678174mr541198e0c.18.1792182510482; Fri, 16 Oct 2026 13:28:30 -0700 (PDT) Received: from localhost ([2601:19c:4a00:897d:3391:ed89:5571:41a8]) by smtp.gmail.com with ESMTPSA id 71dfb90a1353d-5ea9b2fef79sm2422500e0c.1.2026.10.16.13.28.29 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 16 Oct 2026 13:28:29 -0700 (PDT) From: Ray Strode To: libcamera-devel@lists.libcamera.org Cc: kieran.bingham@ideasonboard.com, bod.linux@nxsw.ie, laurent.pinchart@ideasonboard.com Subject: [PATCH v3 02/11] tests: Validate debayer projection geometry Date: Fri, 16 Oct 2026 16:28:09 -0400 Message-ID: <20261016202818.1405645-3-halfline@gmail.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261016202818.1405645-1-halfline@gmail.com> References: <20261015223604.311616-1-halfline@gmail.com> <20261016202818.1405645-1-halfline@gmail.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" From: Ray Strode The GPU debayer path uses a geometry helper to center the sensor image when output dimensions or aspect ratios change. The project lacks a direct regression check for the projection. An incorrect translation can shift the visible scene, including when the output only removes the Bayer border. This commit adds a unit test that projects the expected sensor crop corners and sensor center into output coordinates. Cases cover border removal, downscaling, and horizontal and vertical cropping. Signed-off-by: Ray Strode --- test/debayer-geometry.cpp | 96 +++++++++++++++++++++++++++++++++++++++ test/meson.build | 1 + 2 files changed, 97 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..27458aa1 --- /dev/null +++ b/test/debayer-geometry.cpp @@ -0,0 +1,96 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2026, Red Hat Inc. + * + * Debayer projection tests + */ + +#include +#include +#include +#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 (testProjection()) + return TestFail; + + return TestPass; + } + +private: + int testProjection() + { + static const struct { + Size input; + Size native; + Size output; + Rectangle visible; + } cases[] = { + /* No border or scaling. */ + { { 1920, 1080 }, { 1920, 1080 }, { 1920, 1080 }, { 0, 0, 1920, 1080 } }, + /* Native output splits the Bayer border between both sides. */ + { { 1924, 1080 }, { 1920, 1080 }, { 1920, 1080 }, { 2, 0, 1920, 1080 } }, + { { 1928, 1092 }, { 1920, 1092 }, { 1920, 1092 }, { 4, 0, 1920, 1092 } }, + /* Vertical cropping, with and without downscaling. */ + { { 1928, 1092 }, { 1920, 1092 }, { 1920, 1080 }, { 4, 6, 1920, 1080 } }, + { { 1928, 1092 }, { 1920, 1092 }, { 1280, 720 }, { 4, 6, 1920, 1080 } }, + /* Horizontal cropping for 4:3 and portrait outputs. */ + { { 1928, 1092 }, { 1920, 1092 }, { 640, 480 }, { 236, 0, 1456, 1092 } }, + { { 1928, 1092 }, { 1920, 1092 }, { 512, 768 }, { 600, 0, 728, 1092 } }, + /* A wider output removes more rows from each side. */ + { { 1928, 1092 }, { 1920, 1092 }, { 1280, 640 }, { 4, 66, 1920, 960 } }, + }; + + for (const auto &entry : cases) { + const std::array matrix = + debayerProjection(entry.input, entry.native, entry.output); + int left = entry.visible.x; + int top = entry.visible.y; + int right = left + entry.visible.width; + int bottom = top + entry.visible.height; + const std::pair points[] = { + { { left, top }, { 0, 0 } }, + { { right, top }, { static_cast(entry.output.width), 0 } }, + { { left, bottom }, { 0, static_cast(entry.output.height) } }, + { { right, bottom }, { static_cast(entry.output.width), static_cast(entry.output.height) } }, + { Rectangle(entry.input).center(), Rectangle(entry.output).center() }, + }; + + for (const auto &[sensor, expected] : points) { + double x = 2.0 * sensor.x / entry.input.width - 1.0; + double y = 2.0 * sensor.y / entry.input.height - 1.0; + double w = matrix[3] * x + matrix[7] * y + matrix[15]; + double clipX = (matrix[0] * x + matrix[4] * y + matrix[12]) / w; + double clipY = (matrix[1] * x + matrix[5] * y + matrix[13]) / w; + /* The viewport has the input dimensions, even for smaller outputs. */ + double outputX = (clipX + 1.0) * entry.input.width / 2.0; + double outputY = (clipY + 1.0) * entry.input.height / 2.0; + + if (!std::isfinite(outputX) || !std::isfinite(outputY) || + std::abs(outputX - expected.x) > 0.001 || + std::abs(outputY - expected.y) > 0.001) { + std::cerr << "Projection " << entry.input << " -> " << entry.output + << " maps " << sensor << " to (" << outputX << ", " << outputY + << "), expected " << 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 @@ -52,6 +52,7 @@ 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']},