Skip to content

Commit ace9d11

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 <me@josuah.net>
1 parent 277fb20 commit ace9d11

File tree

12 files changed

+544
-0
lines changed

12 files changed

+544
-0
lines changed
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_formats)
6+
7+
FILE(GLOB app_sources src/*.c)
8+
target_sources(app PRIVATE ${app_sources})

tests/lib/pixel/formats/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/formats/src/main.c

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
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/pixel/formats.h>
10+
#include <zephyr/pixel/print.h>
11+
12+
#define WIDTH 16
13+
#define HEIGHT 16
14+
15+
#define ERROR_MARGIN 10
16+
17+
/*
18+
* To get YUV BT.709 test data:
19+
*
20+
* ffmpeg -y -f lavfi -colorspace bt709 -i color=#rrggbb:2x2:d=3,format=rgb24 \
21+
* -f rawvideo -pix_fmt yuyv422 - | hexdump -C
22+
*
23+
* To get RGB565 test data:
24+
*
25+
* ffmpeg -y -f lavfi -i color=$rgb:2x2:d=3,format=rgb24 \
26+
* -f rawvideo -pix_fmt rgb565 - | hexdump -C
27+
*/
28+
29+
const struct color_ref {
30+
uint8_t rgb24[3];
31+
uint8_t rgb565[2];
32+
uint8_t rgb332[1];
33+
uint8_t yuv24_bt709[3];
34+
uint8_t yuv24_bt601[3];
35+
} reference_data[] = {
36+
37+
/* Primary colors */
38+
{{0x00, 0x00, 0x00}, {0x00, 0x00}, {0x00}, {0x10, 0x80, 0x80}, {0x10, 0x80, 0x80}},
39+
{{0x00, 0x00, 0xff}, {0x00, 0x1f}, {0x03}, {0x20, 0xf0, 0x76}, {0x29, 0xf1, 0x6e}},
40+
{{0x00, 0xff, 0x00}, {0x07, 0xe0}, {0x1c}, {0xad, 0x2a, 0x1a}, {0x9a, 0x2a, 0x35}},
41+
{{0x00, 0xff, 0xff}, {0x07, 0xff}, {0x1f}, {0xbc, 0x9a, 0x10}, {0xb4, 0xa0, 0x23}},
42+
{{0xff, 0x00, 0x00}, {0xf8, 0x00}, {0xe0}, {0x3f, 0x66, 0xf0}, {0x50, 0x5b, 0xee}},
43+
{{0xff, 0x00, 0xff}, {0xf8, 0x1f}, {0xe3}, {0x4e, 0xd6, 0xe6}, {0x69, 0xcb, 0xdc}},
44+
{{0xff, 0xff, 0x00}, {0xff, 0xe0}, {0xfc}, {0xdb, 0x10, 0x8a}, {0xd0, 0x0a, 0x93}},
45+
{{0xff, 0xff, 0xff}, {0xff, 0xff}, {0xff}, {0xeb, 0x80, 0x80}, {0xeb, 0x80, 0x80}},
46+
47+
/* Arbitrary colors */
48+
{{0x00, 0x70, 0xc5}, {0x03, 0x98}, {0x0f}, {0x61, 0xb1, 0x4b}, {0x5e, 0xb5, 0x4d}},
49+
{{0x33, 0x8d, 0xd1}, {0x3c, 0x7a}, {0x33}, {0x7d, 0xa7, 0x56}, {0x7b, 0xab, 0x57}},
50+
{{0x66, 0xa9, 0xdc}, {0x6d, 0x5b}, {0x77}, {0x98, 0x9d, 0x61}, {0x96, 0xa0, 0x61}},
51+
{{0x7d, 0xd2, 0xf7}, {0x86, 0x9e}, {0x7b}, {0xb7, 0x99, 0x59}, {0xb3, 0x9d, 0x5a}},
52+
{{0x97, 0xdb, 0xf9}, {0x9e, 0xde}, {0x9b}, {0xc2, 0x94, 0x61}, {0xbf, 0x97, 0x62}},
53+
{{0xb1, 0xe4, 0xfa}, {0xb7, 0x3f}, {0xbf}, {0xcc, 0x8f, 0x69}, {0xca, 0x91, 0x69}},
54+
{{0x79, 0x29, 0xd2}, {0x79, 0x5a}, {0x67}, {0x4c, 0xc2, 0x9c}, {0x57, 0xbf, 0x96}},
55+
{{0x94, 0x54, 0xdb}, {0x9a, 0xbb}, {0x8b}, {0x6c, 0xb5, 0x97}, {0x75, 0xb3, 0x92}},
56+
{{0xaf, 0x7f, 0xe4}, {0xb3, 0xfc}, {0xaf}, {0x8c, 0xa8, 0x91}, {0x93, 0xa6, 0x8d}},
57+
};
58+
59+
static uint8_t line_in[WIDTH * 4];
60+
static uint8_t line_out[WIDTH * 4];
61+
62+
void test_conversion(const uint8_t *pix_in, size_t pix_in_size, size_t pix_in_step,
63+
const uint8_t *pix_out, size_t pix_out_size, size_t pix_out_step,
64+
void (*fn)(const uint8_t *in, uint8_t *out, uint16_t width))
65+
{
66+
bool done = false;
67+
68+
/* Fill the input line as much as possible */
69+
for (size_t w = 0; w < WIDTH; w += pix_in_step) {
70+
memcpy(&line_in[w * pix_in_size], pix_in, pix_in_size * pix_in_step);
71+
}
72+
73+
/* Perform the conversion to test */
74+
fn(line_in, line_out, WIDTH);
75+
76+
printf("out:");
77+
for (int i = 0; i < pix_out_step * pix_out_size; i++) {
78+
printf(" %02x", line_out[i]);
79+
}
80+
printf("\n");
81+
printf("ref:");
82+
for (int i = 0; i < pix_out_step * pix_out_size; i++) {
83+
printf(" %02x", pix_out[i]);
84+
}
85+
printf("\n");
86+
87+
/* Scan the result against the reference output pixel to make sure it worked */
88+
for (size_t w = 0; w < WIDTH; w += pix_out_step) {
89+
for (int i = 0; w * pix_out_size + i < (w + pix_out_step) * pix_out_size; i++) {
90+
zassert_within(line_out[w * pix_out_size + i], pix_out[i], 9,
91+
"at %u: value 0x%02x, reference 0x%02x",
92+
i, line_out[w * pix_out_size + i], pix_out[i]);
93+
}
94+
95+
/* Make sure we visited that loop */
96+
done = true;
97+
}
98+
99+
zassert_true(done);
100+
}
101+
102+
ZTEST(lib_pixel_format, test_pixel_format_line)
103+
{
104+
for (size_t i = 0; i < ARRAY_SIZE(reference_data); i++) {
105+
/* The current color we are testing */
106+
const struct color_ref *ref = &reference_data[i];
107+
108+
/* Generate very small buffers out of the reference tables */
109+
const uint8_t rgb24[] = {
110+
ref->rgb24[0],
111+
ref->rgb24[1],
112+
ref->rgb24[2],
113+
};
114+
const uint8_t rgb565be[] = {
115+
ref->rgb565[0],
116+
ref->rgb565[1],
117+
};
118+
const uint8_t rgb565le[] = {
119+
ref->rgb565[1],
120+
ref->rgb565[0],
121+
};
122+
const uint8_t rgb332[] = {
123+
ref->rgb332[0],
124+
};
125+
const uint8_t yuyv_bt709[] = {
126+
ref->yuv24_bt709[0],
127+
ref->yuv24_bt709[1],
128+
ref->yuv24_bt709[0],
129+
ref->yuv24_bt709[2],
130+
};
131+
132+
printf("\nColor #%02x%02x%02x\n", ref->rgb24[0], ref->rgb24[1], ref->rgb24[2]);
133+
134+
printf("RGB24 in, RGB565BE out\n");
135+
test_conversion(rgb24, 3, 1, rgb565be, 2, 1, &pixel_rgb24line_to_rgb565beline);
136+
pixel_print_rgb24frame_truecolor(line_in, sizeof(line_in), WIDTH / 2, 2);
137+
pixel_print_rgb565beframe_truecolor(line_out, sizeof(line_out), WIDTH / 2, 2);
138+
139+
printf("RGB24 in, RGB565LE out\n");
140+
test_conversion(rgb24, 3, 1, rgb565le, 2, 1, &pixel_rgb24line_to_rgb565leline);
141+
pixel_print_rgb24frame_truecolor(line_in, sizeof(line_in), WIDTH / 2, 2);
142+
pixel_print_rgb565leframe_truecolor(line_out, sizeof(line_out), WIDTH / 2, 2);
143+
144+
printf("RGB24 in, RGB332 out\n");
145+
test_conversion(rgb24, 3, 1, rgb332, 1, 1, &pixel_rgb24line_to_rgb332line);
146+
pixel_print_rgb24frame_truecolor(line_in, sizeof(line_in), WIDTH / 2, 2);
147+
pixel_print_rgb332frame_truecolor(line_out, sizeof(line_out), WIDTH / 2, 2);
148+
149+
printf("RGB565BE in, RGB24 out\n");
150+
test_conversion(rgb565be, 2, 1, rgb24, 3, 1, &pixel_rgb565beline_to_rgb24line);
151+
pixel_print_rgb565beframe_truecolor(line_in, sizeof(line_in), WIDTH / 2, 2);
152+
pixel_print_rgb24frame_truecolor(line_out, sizeof(line_out), WIDTH / 2, 2);
153+
154+
printf("RGB565LE in, RGB24 out\n");
155+
test_conversion(rgb565le, 2, 1, rgb24, 3, 1, &pixel_rgb565leline_to_rgb24line);
156+
pixel_print_rgb565leframe_truecolor(line_in, sizeof(line_in), WIDTH / 2, 2);
157+
pixel_print_rgb24frame_truecolor(line_out, sizeof(line_out), WIDTH / 2, 2);
158+
159+
printf("RGB24 in, YUYV (BT.709) out\n");
160+
test_conversion(rgb24, 3, 1, yuyv_bt709, 2, 2, &pixel_rgb24line_to_yuyvline_bt709);
161+
pixel_print_rgb24frame_truecolor(line_in, sizeof(line_in), WIDTH / 2, 2);
162+
pixel_print_yuyvframe_bt709_truecolor(line_out, sizeof(line_out), WIDTH / 2, 2);
163+
164+
printf("YUYV (BT.709) in, RGB24 out\n");
165+
test_conversion(yuyv_bt709, 2, 2, rgb24, 3, 1, &pixel_yuyvline_to_rgb24line_bt709);
166+
pixel_print_yuyvframe_bt709_truecolor(line_in, sizeof(line_in), WIDTH / 2, 2);
167+
pixel_print_rgb24frame_truecolor(line_out, sizeof(line_out), WIDTH / 2, 2);
168+
}
169+
}
170+
171+
/* From RGB24 */
172+
static PIXEL_RGB24STREAM_TO_RGB565BESTREAM(step_rgb24_to_rgb565be, WIDTH, HEIGHT);
173+
static PIXEL_RGB24STREAM_TO_RGB565LESTREAM(step_rgb24_to_rgb565le, WIDTH, HEIGHT);
174+
static PIXEL_RGB24STREAM_TO_YUYVSTREAM_BT709(step_rgb24_to_yuyv, WIDTH, HEIGHT);
175+
176+
/* To RGB24 */
177+
static PIXEL_RGB565BESTREAM_TO_RGB24STREAM(step_rgb565be_to_rgb24, WIDTH, HEIGHT);
178+
static PIXEL_RGB565LESTREAM_TO_RGB24STREAM(step_rgb565le_to_rgb24, WIDTH, HEIGHT);
179+
static PIXEL_YUYVSTREAM_TO_RGB24STREAM_BT709(step_yuyv_to_rgb24, WIDTH, HEIGHT);
180+
181+
static uint8_t rgb24frame_in[WIDTH * HEIGHT * 3];
182+
static uint8_t rgb24frame_out[WIDTH * HEIGHT * 3];
183+
184+
ZTEST(lib_pixel_format, test_pixel_format_stream)
185+
{
186+
struct pixel_stream *strm;
187+
188+
/* Generate test input data */
189+
for (size_t i = 0; i < sizeof(rgb24frame_in); i++) {
190+
rgb24frame_in[i] = i / 3;
191+
}
192+
193+
/* Run a pipeline going through all possible steps */
194+
strm = pixel_stream(&step_rgb24_to_rgb565le, &step_rgb565le_to_rgb24,
195+
&step_rgb24_to_rgb565be, &step_rgb565be_to_rgb24,
196+
&step_rgb24_to_yuyv, &step_yuyv_to_rgb24, NULL);
197+
pixel_stream_to_rgb24frame(rgb24frame_in, sizeof(rgb24frame_in), WIDTH,
198+
rgb24frame_out, sizeof(rgb24frame_out), WIDTH, strm);
199+
200+
printf("input:\n");
201+
pixel_print_rgb24frame_truecolor(rgb24frame_in, sizeof(rgb24frame_in), WIDTH, HEIGHT);
202+
203+
printf("output:\n");
204+
pixel_print_rgb24frame_truecolor(rgb24frame_out, sizeof(rgb24frame_out), WIDTH, HEIGHT);
205+
206+
for (int i = 0; i < sizeof(rgb24frame_out); i++) {
207+
/* Precision is not 100% as some conversions steps are lossy */
208+
zassert_within(rgb24frame_in[i], rgb24frame_out[i], ERROR_MARGIN,
209+
"Testing position %u", i);
210+
}
211+
}
212+
213+
ZTEST_SUITE(lib_pixel_format, NULL, NULL, NULL, NULL, NULL);

tests/lib/pixel/formats/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.formats:
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)