@@ -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']},
new file mode 100644
@@ -0,0 +1,102 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2026, Red Hat Inc.
+ *
+ * CPU debayer crop configuration tests
+ */
+
+#include <functional>
+#include <iostream>
+#include <memory>
+#include <tuple>
+#include <utility>
+#include <vector>
+
+#include <libcamera/camera_manager.h>
+#include <libcamera/formats.h>
+#include <libcamera/stream.h>
+
+#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<SwStatsCpu>(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<std::reference_wrapper<const StreamConfiguration>> 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)
new file mode 100644
@@ -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