1 /*
2  * Copyright (c) 2019 Linaro Limited
3  * Copyright 2025 NXP
4  *
5  * SPDX-License-Identifier: Apache-2.0
6  */
7 
8 #include <math.h>
9 
10 #include <zephyr/device.h>
11 #include <zephyr/drivers/display.h>
12 #include <zephyr/drivers/video-controls.h>
13 #include <zephyr/drivers/video.h>
14 #include <zephyr/kernel.h>
15 #include <zephyr/logging/log.h>
16 #include <zephyr/ztest.h>
17 
18 LOG_MODULE_REGISTER(test_pattern, LOG_LEVEL_INF);
19 
20 #define LAB_THRESHOLD ((double)CONFIG_TEST_LAB_THRESHOLD)
21 
22 #define BARS_NUM   8
23 #define PIXELS_NUM 5
24 
25 typedef struct {
26 	double L;
27 	double a;
28 	double b;
29 } CIELAB;
30 
31 /*
32  * This is measured on a real 8-colorbar pattern generated by an ov5640 camera sensor.
33  * For other sensors, it can be slightly different. If it doesn't fit anymore, either
34  * this array or the LAB_THRESHOLD can be modified.
35  */
36 static const CIELAB colorbars_target[] = {
37 	{100.0, 0.0053, -0.0104},     /* White */
38 	{97.1804, -21.2151, 91.3538}, /* Yellow */
39 	{90.1352, -58.4675, 6.0570},  /* Cyan */
40 	{87.7630, -85.9469, 83.2128}, /* Green */
41 	{56.6641, 95.0182, -66.9129}, /* Magenta */
42 	{46.6937, 72.7494, 49.5801},  /* Red */
43 	{27.6487, 71.5662, -97.4712}, /* Blue */
44 	{1.3726, -2.8040, 2.0043},    /* Black */
45 };
46 
rgb888_to_lab(const uint8_t r,const uint8_t g,const uint8_t b)47 static inline CIELAB rgb888_to_lab(const uint8_t r, const uint8_t g, const uint8_t b)
48 {
49 	CIELAB lab;
50 
51 	double r_lin = r / 255.0;
52 	double g_lin = g / 255.0;
53 	double b_lin = b / 255.0;
54 
55 	r_lin = r_lin > 0.04045 ? pow((r_lin + 0.055) / 1.055, 2.4) : r_lin / 12.92;
56 	g_lin = g_lin > 0.04045 ? pow((g_lin + 0.055) / 1.055, 2.4) : g_lin / 12.92;
57 	b_lin = b_lin > 0.04045 ? pow((b_lin + 0.055) / 1.055, 2.4) : b_lin / 12.92;
58 
59 	double x = r_lin * 0.4124 + g_lin * 0.3576 + b_lin * 0.1805;
60 	double y = r_lin * 0.2126 + g_lin * 0.7152 + b_lin * 0.0722;
61 	double z = r_lin * 0.0193 + g_lin * 0.1192 + b_lin * 0.9505;
62 
63 	x /= 0.95047;
64 	z /= 1.08883;
65 
66 	x = x > 0.008856 ? pow(x, 1.0 / 3.0) : (7.787 * x) + (16.0 / 116.0);
67 	y = y > 0.008856 ? pow(y, 1.0 / 3.0) : (7.787 * y) + (16.0 / 116.0);
68 	z = z > 0.008856 ? pow(z, 1.0 / 3.0) : (7.787 * z) + (16.0 / 116.0);
69 
70 	lab.L = 116.0 * y - 16.0;
71 	lab.a = 500.0 * (x - y);
72 	lab.b = 200.0 * (y - z);
73 
74 	return lab;
75 }
76 
xrgb32_to_lab(const uint32_t color)77 static inline CIELAB xrgb32_to_lab(const uint32_t color)
78 {
79 	uint8_t r = (color >> 16) & 0xFF;
80 	uint8_t g = (color >> 8) & 0xFF;
81 	uint8_t b = color & 0xFF;
82 
83 	return rgb888_to_lab(r, g, b);
84 }
85 
rgb565_to_lab(const uint16_t color)86 static inline CIELAB rgb565_to_lab(const uint16_t color)
87 {
88 	uint8_t r5 = (color >> 11) & 0x1F;
89 	uint8_t g6 = (color >> 5) & 0x3F;
90 	uint8_t b5 = color & 0x1F;
91 
92 	/* Convert RGB565 to RGB888 */
93 	uint8_t r = (r5 * 255) / 31;
94 	uint8_t g = (g6 * 255) / 63;
95 	uint8_t b = (b5 * 255) / 31;
96 
97 	return rgb888_to_lab(r, g, b);
98 }
99 
sum_lab(CIELAB * sum,const CIELAB lab)100 static inline void sum_lab(CIELAB *sum, const CIELAB lab)
101 {
102 	sum->L += lab.L;
103 	sum->a += lab.a;
104 	sum->b += lab.b;
105 }
106 
average_lab(CIELAB * lab,const uint32_t count)107 static inline void average_lab(CIELAB *lab, const uint32_t count)
108 {
109 	if (count > 0) {
110 		lab->L /= count;
111 		lab->a /= count;
112 		lab->b /= count;
113 	}
114 }
115 
deltaE(const CIELAB lab1,const CIELAB lab2)116 static inline double deltaE(const CIELAB lab1, const CIELAB lab2)
117 {
118 	return sqrt(pow(lab1.L - lab2.L, 2) + pow(lab1.a - lab2.a, 2) + pow(lab1.b - lab2.b, 2));
119 }
120 
121 /*
122  * As color values may vary near the boundary of each bar and also, for computational
123  * efficiency, check only a small number of pixels (PIXELS_NUM) in the middle of each bar.
124  */
is_colorbar_ok(const uint8_t * const buf,const struct video_format * fmt)125 static inline bool is_colorbar_ok(const uint8_t *const buf, const struct video_format *fmt)
126 {
127 	int i;
128 	int bw = fmt->width / BARS_NUM;
129 	CIELAB colorbars[BARS_NUM] = {0};
130 
131 	for (int h = 0; h < fmt->height; h++) {
132 		for (i = 0; i < BARS_NUM; i++) {
133 			if (fmt->pixelformat == VIDEO_PIX_FMT_XRGB32) {
134 				uint32_t *pixel =
135 					(uint32_t *)&buf[4 * (h * fmt->width + bw / 2 + i * bw)];
136 
137 				for (int j = -PIXELS_NUM / 2; j <= PIXELS_NUM / 2; j++) {
138 					sum_lab(&colorbars[i], xrgb32_to_lab(*(pixel + j)));
139 				}
140 			} else if (fmt->pixelformat == VIDEO_PIX_FMT_RGB565) {
141 				uint16_t *pixel =
142 					(uint16_t *)&buf[2 * (h * fmt->width + bw / 2 + i * bw)];
143 
144 				for (int j = -PIXELS_NUM / 2; j <= PIXELS_NUM / 2; j++) {
145 					sum_lab(&colorbars[i], rgb565_to_lab(*(pixel + j)));
146 				}
147 			} else {
148 				printk("Format %d is not supported", fmt->pixelformat);
149 				return false;
150 			}
151 		}
152 	}
153 
154 	for (i = 0; i < BARS_NUM; i++) {
155 		average_lab(&colorbars[i], PIXELS_NUM * fmt->height);
156 		if (deltaE(colorbars[i], colorbars_target[i]) > LAB_THRESHOLD) {
157 			return false;
158 		}
159 	}
160 
161 	return true;
162 }
163 
164 static const struct device *const video_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_camera));
165 struct video_format fmt;
166 
test_pattern_setup(void)167 static void *test_pattern_setup(void)
168 {
169 	struct video_buffer *vbuf = &(struct video_buffer){};
170 	struct video_caps caps = {
171 		.type = VIDEO_BUF_TYPE_OUTPUT,
172 	};
173 	struct video_control ctrl = {
174 		.id = VIDEO_CID_TEST_PATTERN, .val = CONFIG_TEST_PATTERN_CTRL,
175 	};
176 	int ret;
177 
178 	zassert(device_is_ready(video_dev),  "device initialization failed");
179 
180 	ret = video_get_caps(video_dev, &caps);
181 	zassert_ok(ret, "getting video capabilities failed");
182 
183 	fmt.type = VIDEO_BUF_TYPE_OUTPUT;
184 	ret = video_get_format(video_dev, &fmt);
185 	zassert_ok(ret, "getting default video format failed");
186 
187 	if (CONFIG_TEST_FRAME_HEIGHT > 0) {
188 		fmt.height = CONFIG_TEST_FRAME_HEIGHT;
189 	}
190 	if (CONFIG_TEST_FRAME_WIDTH > 0) {
191 		fmt.width = CONFIG_TEST_FRAME_WIDTH;
192 	}
193 	if (strcmp(CONFIG_TEST_PIXEL_FORMAT, "") != 0) {
194 		fmt.pixelformat = VIDEO_FOURCC_FROM_STR(CONFIG_TEST_PIXEL_FORMAT);
195 	}
196 
197 	LOG_INF("Video format: %s %ux%u",
198 		VIDEO_FOURCC_TO_STR(fmt.pixelformat), fmt.width, fmt.height);
199 
200 	ret = video_set_format(video_dev, &fmt);
201 	zassert_ok(ret, "setting video format failed");
202 
203 	ret = video_set_ctrl(video_dev, &ctrl);
204 	zassert_ok(ret, "setting test pattern");
205 
206 	/* Alloc video buffers and enqueue for capture */
207 	zassert(caps.min_vbuf_count <= CONFIG_VIDEO_BUFFER_POOL_NUM_MAX,
208 		"not enough buffers");
209 	zassert(fmt.size <= (CONFIG_VIDEO_BUFFER_POOL_HEAP_SIZE / CONFIG_VIDEO_BUFFER_POOL_NUM_MAX),
210 		"buffers too large");
211 
212 	for (int i = 0; i < CONFIG_VIDEO_BUFFER_POOL_NUM_MAX; i++) {
213 		vbuf = video_buffer_aligned_alloc(fmt.size, CONFIG_VIDEO_BUFFER_POOL_ALIGN,
214 						  K_NO_WAIT);
215 		zassert_not_null(vbuf);
216 
217 		vbuf->type = VIDEO_BUF_TYPE_OUTPUT;
218 
219 		ret = video_enqueue(video_dev, vbuf);
220 		zassert_ok(ret);
221 	}
222 
223 	LOG_INF("Device %s configured starting capture", video_dev->name);
224 
225 	ret = video_stream_start(video_dev, VIDEO_BUF_TYPE_OUTPUT);
226 	zassert_ok(ret);
227 
228 	return NULL;
229 }
230 
test_pattern_after(void *)231 void test_pattern_after(void *)
232 {
233 	int ret;
234 
235 	ret = video_stream_stop(video_dev, VIDEO_BUF_TYPE_OUTPUT);
236 	zassert_ok(ret);
237 }
238 
ZTEST(test_pattern,test_pattern_frames)239 ZTEST(test_pattern, test_pattern_frames)
240 {
241 	struct video_buffer *vbuf = &(struct video_buffer){
242 		.type = VIDEO_BUF_TYPE_OUTPUT
243 	};
244 	size_t valid = 0;
245 	int ret;
246 
247 	for (size_t i = 0; i < CONFIG_TEST_FRAMES_TOTAL; i++) {
248 		ret = video_dequeue(video_dev, &vbuf, K_FOREVER);
249 		zassert_ok(ret);
250 
251 		LOG_INF("Got frame, testing color bars");
252 
253 		valid += is_colorbar_ok(vbuf->buffer, &fmt);
254 		if (valid >= CONFIG_TEST_FRAMES_VALID) {
255 			LOG_INF("Got %u valid frames out of %u, stopping the test", valid, i + 1);
256 			break;
257 		}
258 
259 		ret = video_enqueue(video_dev, vbuf);
260 		zassert_ok(ret);
261 	}
262 
263 	zassert_equal(valid, CONFIG_TEST_FRAMES_VALID,
264 		      "there should be at least %u valid frames out of %u",
265 		      CONFIG_TEST_FRAMES_VALID, CONFIG_TEST_FRAMES_TOTAL);
266 }
267 
268 ZTEST_SUITE(test_pattern, NULL, test_pattern_setup, NULL, test_pattern_after, NULL);
269