@@ -1,63 +1,106 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2026, Red Hat Inc.
*
* Sensor crop tests
*/
#include <iostream>
#include <libcamera/geometry.h>
#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)