Skip to content

Commit 43f5a22

Browse files
committed
tests: lib: pixel: add unit tests for format conversion
The tests use data generated by the ffmpeg command line utilty in order to avoid bias by having the same person implementing the tests and the source code. Signed-off-by: Josuah Demangeon <[email protected]>
1 parent 96cc37f commit 43f5a22

File tree

15 files changed

+499
-0
lines changed

15 files changed

+499
-0
lines changed

tests/lib/pixel/bayer/CMakeLists.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
3+
cmake_minimum_required(VERSION 3.20.0)
4+
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
5+
project(lib_pixel_convert)
6+
7+
FILE(GLOB app_sources src/*.c)
8+
target_sources(app PRIVATE ${app_sources})

tests/lib/pixel/bayer/prj.conf

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
CONFIG_ASSERT=y
2+
CONFIG_PIXEL_LOG_LEVEL_DBG=y
3+
CONFIG_ZTEST=y
4+
CONFIG_PIXEL=y

tests/lib/pixel/bayer/src/main.c

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/*
2+
* Copyright (c) 2025 tinyVision.ai Inc.
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
#include <zephyr/kernel.h>
7+
#include <zephyr/random/random.h>
8+
#include <zephyr/ztest.h>
9+
#include <zephyr/drivers/video.h>
10+
#include <zephyr/pixel/print.h>
11+
#include <zephyr/pixel/image.h>
12+
13+
#define WIDTH 16
14+
#define HEIGHT 16
15+
16+
#define ERROR_MARGIN 13
17+
18+
static uint8_t bayerframe_in[WIDTH * HEIGHT * 1];
19+
static uint8_t rgb24frame_out[WIDTH * HEIGHT * 3];
20+
21+
void test_bayer(uint32_t fourcc, uint32_t window_size, uint32_t expected_color)
22+
{
23+
uint8_t r = expected_color >> 16;
24+
uint8_t g = expected_color >> 8;
25+
uint8_t b = expected_color >> 0;
26+
struct pixel_image img;
27+
int ret;
28+
29+
pixel_image_from_buffer(&img, bayerframe_in, sizeof(bayerframe_in), WIDTH, HEIGHT, fourcc);
30+
31+
printf("input:\n");
32+
pixel_image_print_truecolor(&img);
33+
34+
ret = pixel_image_debayer(&img, window_size);
35+
zassert_ok(ret);
36+
37+
pixel_image_to_buffer(&img, rgb24frame_out, sizeof(rgb24frame_out));
38+
39+
printf("output: (expecting #%06x, R:%02x G:%02x B:%02x)\n", expected_color, r, g, b);
40+
pixel_image_print_truecolor(&img);
41+
42+
for (int i = 0; i < sizeof(rgb24frame_out) / 3; i++) {
43+
uint8_t out_r = rgb24frame_out[i * 3 + 0];
44+
uint8_t out_g = rgb24frame_out[i * 3 + 1];
45+
uint8_t out_b = rgb24frame_out[i * 3 + 2];
46+
char *s = VIDEO_FOURCC_TO_STR(fourcc);
47+
48+
zassert_equal(r, out_r, "R: %s: expected 0x%02x, obtained 0x%02x", s, r, out_r);
49+
zassert_equal(g, out_g, "G: %s: expected 0x%02x, obtained 0x%02x", s, g, out_g);
50+
zassert_equal(b, out_b, "B: %s: expected 0x%02x, obtained 0x%02x", s, b, out_b);
51+
}
52+
}
53+
54+
ZTEST(lib_pixel_bayer, test_pixel_bayer_operation)
55+
{
56+
/* Generate test input data for 2x2 debayer */
57+
for (size_t h = 0; h < HEIGHT; h++) {
58+
memset(bayerframe_in + h * WIDTH, h % 2 ? 0xff : 0x00, WIDTH * 1);
59+
}
60+
61+
test_bayer(VIDEO_PIX_FMT_RGGB8, 2, 0x007fff);
62+
test_bayer(VIDEO_PIX_FMT_GRBG8, 2, 0x007fff);
63+
test_bayer(VIDEO_PIX_FMT_BGGR8, 2, 0xff7f00);
64+
test_bayer(VIDEO_PIX_FMT_GBRG8, 2, 0xff7f00);
65+
66+
/* Generate test input data for 3x3 debayer */
67+
for (size_t h = 0; h < HEIGHT; h++) {
68+
for (size_t w = 0; w < WIDTH; w++) {
69+
bayerframe_in[h * WIDTH + w] = (h + w) % 2 ? 0xff : 0x00;
70+
}
71+
}
72+
73+
test_bayer(VIDEO_PIX_FMT_RGGB8, 3, 0x00ff00);
74+
test_bayer(VIDEO_PIX_FMT_GBRG8, 3, 0xff00ff);
75+
test_bayer(VIDEO_PIX_FMT_BGGR8, 3, 0x00ff00);
76+
test_bayer(VIDEO_PIX_FMT_GRBG8, 3, 0xff00ff);
77+
}
78+
79+
ZTEST_SUITE(lib_pixel_bayer, NULL, NULL, NULL, NULL, NULL);

tests/lib/pixel/bayer/testcase.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
tests:
2+
libraries.pixel.bayer:
3+
tags:
4+
- pixel
5+
integration_platforms:
6+
- qemu_cortex_m3
7+
- native_sim
8+
extra_configs:
9+
- CONFIG_PIXEL_PRINT_NONE=y
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
3+
cmake_minimum_required(VERSION 3.20.0)
4+
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
5+
project(lib_pixel_convert)
6+
7+
FILE(GLOB app_sources src/*.c)
8+
target_sources(app PRIVATE ${app_sources})

tests/lib/pixel/convert/prj.conf

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
CONFIG_ASSERT=y
2+
CONFIG_PIXEL_LOG_LEVEL_DBG=y
3+
CONFIG_ZTEST=y
4+
CONFIG_PIXEL=y

tests/lib/pixel/convert/src/main.c

Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
/*
2+
* Copyright (c) 2025 tinyVision.ai Inc.
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
#include <zephyr/kernel.h>
7+
#include <zephyr/random/random.h>
8+
#include <zephyr/ztest.h>
9+
#include <zephyr/drivers/video.h>
10+
#include <zephyr/pixel/convert.h>
11+
#include <zephyr/pixel/print.h>
12+
#include <zephyr/pixel/image.h>
13+
14+
#define WIDTH 16
15+
#define HEIGHT 16
16+
17+
#define ERROR_MARGIN 13
18+
19+
/*
20+
* To get YUV BT.709 test data:
21+
*
22+
* ffmpeg -y -f lavfi -colorspace bt709 -i color=#RRGGBB:2x2:d=3,format=rgb24 \
23+
* -f rawvideo -pix_fmt yuyv422 - | hexdump -C
24+
*
25+
* To get RGB565 test data:
26+
*
27+
* ffmpeg -y -f lavfi -i color=#RRGGBB:2x2:d=3,format=rgb24 \
28+
* -f rawvideo -pix_fmt rgb565 - | hexdump -C
29+
*/
30+
31+
const struct color_ref {
32+
uint8_t rgb24[3];
33+
uint8_t rgb565[2];
34+
uint8_t rgb332[1];
35+
uint8_t yuv24_bt709[3];
36+
uint8_t yuv24_bt601[3];
37+
} reference_data[] = {
38+
39+
/* Primary colors */
40+
{{0x00, 0x00, 0x00}, {0x00, 0x00}, {0x00}, {0x10, 0x80, 0x80}, {0x10, 0x80, 0x80}},
41+
{{0x00, 0x00, 0xff}, {0x00, 0x1f}, {0x03}, {0x20, 0xf0, 0x76}, {0x29, 0xf1, 0x6e}},
42+
{{0x00, 0xff, 0x00}, {0x07, 0xe0}, {0x1c}, {0xad, 0x2a, 0x1a}, {0x9a, 0x2a, 0x35}},
43+
{{0x00, 0xff, 0xff}, {0x07, 0xff}, {0x1f}, {0xbc, 0x9a, 0x10}, {0xb4, 0xa0, 0x23}},
44+
{{0xff, 0x00, 0x00}, {0xf8, 0x00}, {0xe0}, {0x3f, 0x66, 0xf0}, {0x50, 0x5b, 0xee}},
45+
{{0xff, 0x00, 0xff}, {0xf8, 0x1f}, {0xe3}, {0x4e, 0xd6, 0xe6}, {0x69, 0xcb, 0xdc}},
46+
{{0xff, 0xff, 0x00}, {0xff, 0xe0}, {0xfc}, {0xdb, 0x10, 0x8a}, {0xd0, 0x0a, 0x93}},
47+
{{0xff, 0xff, 0xff}, {0xff, 0xff}, {0xff}, {0xeb, 0x80, 0x80}, {0xeb, 0x80, 0x80}},
48+
49+
/* Arbitrary colors */
50+
{{0x00, 0x70, 0xc5}, {0x03, 0x98}, {0x0f}, {0x61, 0xb1, 0x4b}, {0x5e, 0xb5, 0x4d}},
51+
{{0x33, 0x8d, 0xd1}, {0x3c, 0x7a}, {0x33}, {0x7d, 0xa7, 0x56}, {0x7b, 0xab, 0x57}},
52+
{{0x66, 0xa9, 0xdc}, {0x6d, 0x5b}, {0x77}, {0x98, 0x9d, 0x61}, {0x96, 0xa0, 0x61}},
53+
{{0x7d, 0xd2, 0xf7}, {0x86, 0x9e}, {0x7b}, {0xb7, 0x99, 0x59}, {0xb3, 0x9d, 0x5a}},
54+
{{0x97, 0xdb, 0xf9}, {0x9e, 0xde}, {0x9b}, {0xc2, 0x94, 0x61}, {0xbf, 0x97, 0x62}},
55+
{{0xb1, 0xe4, 0xfa}, {0xb7, 0x3f}, {0xbf}, {0xcc, 0x8f, 0x69}, {0xca, 0x91, 0x69}},
56+
{{0x79, 0x29, 0xd2}, {0x79, 0x5a}, {0x67}, {0x4c, 0xc2, 0x9c}, {0x57, 0xbf, 0x96}},
57+
{{0x94, 0x54, 0xdb}, {0x9a, 0xbb}, {0x8b}, {0x6c, 0xb5, 0x97}, {0x75, 0xb3, 0x92}},
58+
{{0xaf, 0x7f, 0xe4}, {0xb3, 0xfc}, {0xaf}, {0x8c, 0xa8, 0x91}, {0x93, 0xa6, 0x8d}},
59+
};
60+
61+
static uint8_t line_in[WIDTH * 4];
62+
static uint8_t line_out[WIDTH * 4];
63+
64+
void test_conversion(const uint8_t *pix_in, uint32_t fourcc_in, size_t pix_in_step,
65+
const uint8_t *pix_out, uint32_t fourcc_out, size_t pix_out_step,
66+
void (*fn)(const uint8_t *in, uint8_t *out, uint16_t width))
67+
{
68+
size_t pix_in_size = video_bits_per_pixel(fourcc_in) / BITS_PER_BYTE;
69+
size_t pix_out_size = video_bits_per_pixel(fourcc_out) / BITS_PER_BYTE;
70+
bool done = false;
71+
72+
/* Fill the input line as much as possible */
73+
for (size_t w = 0; w < WIDTH; w += pix_in_step) {
74+
memcpy(&line_in[w * pix_in_size], pix_in, pix_in_size * pix_in_step);
75+
}
76+
77+
/* Perform the conversion to test */
78+
fn(line_in, line_out, WIDTH);
79+
80+
printf("\n");
81+
82+
printf("out:");
83+
for (int i = 0; i < pix_out_step * pix_out_size; i++) {
84+
printf(" %02x", line_out[i]);
85+
}
86+
printf(" |");
87+
pixel_print_buffer_truecolor(line_out, sizeof(line_out), WIDTH / 2, 2, fourcc_out);
88+
89+
printf("ref:");
90+
for (int i = 0; i < pix_out_step * pix_out_size; i++) {
91+
printf(" %02x", pix_out[i]);
92+
}
93+
printf(" |");
94+
pixel_print_buffer_truecolor(line_in, sizeof(line_in), WIDTH / 2, 2, fourcc_in);
95+
96+
/* Scan the result against the reference output pixel to make sure it worked */
97+
for (size_t w = 0; w < WIDTH; w += pix_out_step) {
98+
for (int i = 0; w * pix_out_size + i < (w + pix_out_step) * pix_out_size; i++) {
99+
zassert_within(line_out[w * pix_out_size + i], pix_out[i], 9,
100+
"at %u: value 0x%02x, reference 0x%02x",
101+
i, line_out[w * pix_out_size + i], pix_out[i]);
102+
}
103+
104+
/* Make sure we visited that loop */
105+
done = true;
106+
}
107+
108+
zassert_true(done);
109+
}
110+
111+
ZTEST(lib_pixel_convert, test_pixel_convert_line)
112+
{
113+
for (size_t i = 0; i < ARRAY_SIZE(reference_data); i++) {
114+
/* The current color we are testing */
115+
const struct color_ref *ref = &reference_data[i];
116+
117+
/* Generate very small buffers out of the reference tables */
118+
const uint8_t rgb24[] = {
119+
ref->rgb24[0],
120+
ref->rgb24[1],
121+
ref->rgb24[2],
122+
};
123+
const uint8_t rgb565be[] = {
124+
ref->rgb565[0],
125+
ref->rgb565[1],
126+
};
127+
const uint8_t rgb565le[] = {
128+
ref->rgb565[1],
129+
ref->rgb565[0],
130+
};
131+
const uint8_t rgb332[] = {
132+
ref->rgb332[0],
133+
};
134+
const uint8_t yuv24_bt709[] = {
135+
ref->yuv24_bt709[0],
136+
ref->yuv24_bt709[1],
137+
ref->yuv24_bt709[2],
138+
};
139+
const uint8_t yuyv_bt709[] = {
140+
ref->yuv24_bt709[0],
141+
ref->yuv24_bt709[1],
142+
ref->yuv24_bt709[0],
143+
ref->yuv24_bt709[2],
144+
};
145+
146+
printf("\nColor #%02x%02x%02x\n", ref->rgb24[0], ref->rgb24[1], ref->rgb24[2]);
147+
148+
test_conversion(rgb24, VIDEO_PIX_FMT_RGB24, 1, rgb565be,
149+
VIDEO_PIX_FMT_RGB565X, 1, &pixel_line_rgb24_to_rgb565be);
150+
test_conversion(rgb24, VIDEO_PIX_FMT_RGB24, 1, rgb565le,
151+
VIDEO_PIX_FMT_RGB565, 1, &pixel_line_rgb24_to_rgb565le);
152+
test_conversion(rgb24, VIDEO_PIX_FMT_RGB24, 1, rgb332,
153+
VIDEO_PIX_FMT_RGB332, 1, &pixel_line_rgb24_to_rgb332);
154+
test_conversion(rgb565be, VIDEO_PIX_FMT_RGB565X, 1, rgb24,
155+
VIDEO_PIX_FMT_RGB24, 1, &pixel_line_rgb565be_to_rgb24);
156+
test_conversion(rgb565le, VIDEO_PIX_FMT_RGB565, 1, rgb24,
157+
VIDEO_PIX_FMT_RGB24, 1, &pixel_line_rgb565le_to_rgb24);
158+
test_conversion(rgb24, VIDEO_PIX_FMT_RGB24, 1, yuyv_bt709,
159+
VIDEO_PIX_FMT_YUYV, 2, &pixel_line_rgb24_to_yuyv_bt709);
160+
test_conversion(yuyv_bt709, VIDEO_PIX_FMT_YUYV, 2, rgb24,
161+
VIDEO_PIX_FMT_RGB24, 1, &pixel_line_yuyv_to_rgb24_bt709);
162+
test_conversion(rgb24, VIDEO_PIX_FMT_RGB24, 1, yuv24_bt709,
163+
VIDEO_PIX_FMT_YUV24, 1, &pixel_line_rgb24_to_yuv24_bt709);
164+
test_conversion(yuv24_bt709, VIDEO_PIX_FMT_YUV24, 1, rgb24,
165+
VIDEO_PIX_FMT_RGB24, 1, &pixel_line_yuv24_to_rgb24_bt709);
166+
test_conversion(yuv24_bt709, VIDEO_PIX_FMT_YUV24, 1, yuyv_bt709,
167+
VIDEO_PIX_FMT_YUYV, 2, &pixel_line_yuv24_to_yuyv);
168+
test_conversion(yuyv_bt709, VIDEO_PIX_FMT_YUYV, 2, yuv24_bt709,
169+
VIDEO_PIX_FMT_YUV24, 1, &pixel_line_yuyv_to_yuv24);
170+
}
171+
}
172+
173+
static uint8_t rgb24frame_in[WIDTH * HEIGHT * 3];
174+
static uint8_t rgb24frame_out[WIDTH * HEIGHT * 3];
175+
176+
ZTEST(lib_pixel_convert, test_pixel_convert_operation)
177+
{
178+
struct pixel_image img;
179+
int ret;
180+
181+
/* Generate test input data */
182+
for (size_t i = 0; i < sizeof(rgb24frame_in); i++) {
183+
rgb24frame_in[i] = i / 3;
184+
}
185+
186+
pixel_image_from_buffer(&img, rgb24frame_in, sizeof(rgb24frame_in),
187+
WIDTH, HEIGHT, VIDEO_PIX_FMT_RGB24);
188+
189+
printf("input:\n");
190+
pixel_image_print_truecolor(&img);
191+
192+
ret = pixel_image_convert(&img, VIDEO_PIX_FMT_RGB24);
193+
zassert_ok(ret);
194+
195+
/* Test the RGB24 <-> RGB565 conversion */
196+
ret = pixel_image_convert(&img, VIDEO_PIX_FMT_RGB565);
197+
zassert_ok(ret);
198+
ret = pixel_image_convert(&img, VIDEO_PIX_FMT_RGB24);
199+
zassert_ok(ret);
200+
201+
/* Test the RGB24 <-> RGB565X conversion */
202+
ret = pixel_image_convert(&img, VIDEO_PIX_FMT_RGB565X);
203+
zassert_ok(ret);
204+
ret = pixel_image_convert(&img, VIDEO_PIX_FMT_RGB24);
205+
zassert_ok(ret);
206+
207+
/* Test the RGB24 <-> YUV24 conversion */
208+
ret = pixel_image_convert(&img, VIDEO_PIX_FMT_YUV24);
209+
zassert_ok(ret);
210+
ret = pixel_image_convert(&img, VIDEO_PIX_FMT_RGB24);
211+
zassert_ok(ret);
212+
213+
/* Test the YUYV <-> YUV24 conversion */
214+
ret = pixel_image_convert(&img, VIDEO_PIX_FMT_YUYV);
215+
zassert_ok(ret);
216+
ret = pixel_image_convert(&img, VIDEO_PIX_FMT_YUV24);
217+
zassert_ok(ret);
218+
ret = pixel_image_convert(&img, VIDEO_PIX_FMT_YUYV);
219+
zassert_ok(ret);
220+
ret = pixel_image_convert(&img, VIDEO_PIX_FMT_RGB24);
221+
zassert_ok(ret);
222+
223+
pixel_image_to_buffer(&img, rgb24frame_out, sizeof(rgb24frame_out));
224+
225+
printf("output:\n");
226+
pixel_image_print_truecolor(&img);
227+
228+
for (int i = 0; i < sizeof(rgb24frame_out); i++) {
229+
/* Precision is not 100% as some conversions steps are lossy */
230+
zassert_within(rgb24frame_in[i], rgb24frame_out[i], ERROR_MARGIN,
231+
"Testing position %u", i);
232+
}
233+
}
234+
235+
ZTEST_SUITE(lib_pixel_convert, NULL, NULL, NULL, NULL, NULL);

tests/lib/pixel/convert/testcase.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
tests:
2+
libraries.pixel.convert:
3+
tags:
4+
- pixel
5+
integration_platforms:
6+
- qemu_cortex_m3
7+
- native_sim
8+
extra_configs:
9+
- CONFIG_PIXEL_PRINT_NONE=y

tests/lib/pixel/kernel/CMakeLists.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
3+
cmake_minimum_required(VERSION 3.20.0)
4+
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
5+
project(lib_pixel_kernel)
6+
7+
FILE(GLOB app_sources src/*.c)
8+
target_sources(app PRIVATE ${app_sources})

tests/lib/pixel/kernel/prj.conf

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
CONFIG_ASSERT=y
2+
CONFIG_PIXEL_LOG_LEVEL_DBG=y
3+
CONFIG_ZTEST=y
4+
CONFIG_PIXEL=y

0 commit comments

Comments
 (0)