1 /*
2  * Copyright 2024 NXP
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #ifndef TEST_PATTERN_CHECK_H_
8 #define TEST_PATTERN_CHECK_H_
9 
10 #include <math.h>
11 
12 #include <zephyr/drivers/video.h>
13 
14 #define LAB_THRESHOLD 10.0
15 
16 #define BARS_NUM   8
17 #define PIXELS_NUM 5
18 
19 typedef struct {
20 	double L;
21 	double a;
22 	double b;
23 } CIELAB;
24 
25 /*
26  * This is measured on a real 8-colorbar pattern generated by an ov5640 camera sensor.
27  * For other sensors, it can be slightly different. If it doesn't fit anymore, either
28  * this array or the LAB_THRESHOLD can be modified.
29  *
30  * {White, Yellow, Cyan, Green, Magenta, Red, Blue, Black}
31  */
32 static const CIELAB colorbars_target[] = {
33 	{100.0, 0.0053, -0.0104},     {97.1804, -21.2151, 91.3538}, {90.1352, -58.4675, 6.0570},
34 	{87.7630, -85.9469, 83.2128}, {56.6641, 95.0182, -66.9129}, {46.6937, 72.7494, 49.5801},
35 	{27.6487, 71.5662, -97.4712}, {1.3726, -2.8040, 2.0043}};
36 
rgb888_to_lab(const uint8_t r,const uint8_t g,const uint8_t b)37 static inline CIELAB rgb888_to_lab(const uint8_t r, const uint8_t g, const uint8_t b)
38 {
39 	CIELAB lab;
40 
41 	double r_lin = r / 255.0;
42 	double g_lin = g / 255.0;
43 	double b_lin = b / 255.0;
44 
45 	r_lin = r_lin > 0.04045 ? pow((r_lin + 0.055) / 1.055, 2.4) : r_lin / 12.92;
46 	g_lin = g_lin > 0.04045 ? pow((g_lin + 0.055) / 1.055, 2.4) : g_lin / 12.92;
47 	b_lin = b_lin > 0.04045 ? pow((b_lin + 0.055) / 1.055, 2.4) : b_lin / 12.92;
48 
49 	double x = r_lin * 0.4124 + g_lin * 0.3576 + b_lin * 0.1805;
50 	double y = r_lin * 0.2126 + g_lin * 0.7152 + b_lin * 0.0722;
51 	double z = r_lin * 0.0193 + g_lin * 0.1192 + b_lin * 0.9505;
52 
53 	x /= 0.95047;
54 	z /= 1.08883;
55 
56 	x = x > 0.008856 ? pow(x, 1.0 / 3.0) : (7.787 * x) + (16.0 / 116.0);
57 	y = y > 0.008856 ? pow(y, 1.0 / 3.0) : (7.787 * y) + (16.0 / 116.0);
58 	z = z > 0.008856 ? pow(z, 1.0 / 3.0) : (7.787 * z) + (16.0 / 116.0);
59 
60 	lab.L = 116.0 * y - 16.0;
61 	lab.a = 500.0 * (x - y);
62 	lab.b = 200.0 * (y - z);
63 
64 	return lab;
65 }
66 
xrgb32_to_lab(const uint32_t color)67 static inline CIELAB xrgb32_to_lab(const uint32_t color)
68 {
69 	uint8_t r = (color >> 16) & 0xFF;
70 	uint8_t g = (color >> 8) & 0xFF;
71 	uint8_t b = color & 0xFF;
72 
73 	return rgb888_to_lab(r, g, b);
74 }
75 
rgb565_to_lab(const uint16_t color)76 static inline CIELAB rgb565_to_lab(const uint16_t color)
77 {
78 	uint8_t r5 = (color >> 11) & 0x1F;
79 	uint8_t g6 = (color >> 5) & 0x3F;
80 	uint8_t b5 = color & 0x1F;
81 
82 	/* Convert RGB565 to RGB888 */
83 	uint8_t r = (r5 * 255) / 31;
84 	uint8_t g = (g6 * 255) / 63;
85 	uint8_t b = (b5 * 255) / 31;
86 
87 	return rgb888_to_lab(r, g, b);
88 }
89 
sum_lab(CIELAB * sum,const CIELAB lab)90 static inline void sum_lab(CIELAB *sum, const CIELAB lab)
91 {
92 	sum->L += lab.L;
93 	sum->a += lab.a;
94 	sum->b += lab.b;
95 }
96 
average_lab(CIELAB * lab,const uint32_t count)97 static inline void average_lab(CIELAB *lab, const uint32_t count)
98 {
99 	if (count > 0) {
100 		lab->L /= count;
101 		lab->a /= count;
102 		lab->b /= count;
103 	}
104 }
105 
deltaE(const CIELAB lab1,const CIELAB lab2)106 static inline double deltaE(const CIELAB lab1, const CIELAB lab2)
107 {
108 	return sqrt(pow(lab1.L - lab2.L, 2) + pow(lab1.a - lab2.a, 2) + pow(lab1.b - lab2.b, 2));
109 }
110 
111 /*
112  * As color values may vary near the boundary of each bar and also, for computational
113  * efficiency, check only a small number of pixels (PIXELS_NUM) in the middle of each bar.
114  */
is_colorbar_ok(const uint8_t * const buf,const struct video_format fmt)115 static inline bool is_colorbar_ok(const uint8_t *const buf, const struct video_format fmt)
116 {
117 	int i;
118 	int bw = fmt.width / BARS_NUM;
119 	CIELAB colorbars[BARS_NUM] = {0};
120 
121 	for (int h = 0; h < fmt.height; h++) {
122 		for (i = 0; i < BARS_NUM; i++) {
123 			if (fmt.pixelformat == VIDEO_PIX_FMT_XRGB32) {
124 				uint32_t *pixel =
125 					(uint32_t *)&buf[4 * (h * fmt.width + bw / 2 + i * bw)];
126 
127 				for (int j = -PIXELS_NUM / 2; j <= PIXELS_NUM / 2; j++) {
128 					sum_lab(&colorbars[i], xrgb32_to_lab(*(pixel + j)));
129 				}
130 			} else if (fmt.pixelformat == VIDEO_PIX_FMT_RGB565) {
131 				uint16_t *pixel =
132 					(uint16_t *)&buf[2 * (h * fmt.width + bw / 2 + i * bw)];
133 
134 				for (int j = -PIXELS_NUM / 2; j <= PIXELS_NUM / 2; j++) {
135 					sum_lab(&colorbars[i], rgb565_to_lab(*(pixel + j)));
136 				}
137 			} else {
138 				printk("Format %d is not supported", fmt.pixelformat);
139 				return false;
140 			}
141 		}
142 	}
143 
144 	for (i = 0; i < BARS_NUM; i++) {
145 		average_lab(&colorbars[i], PIXELS_NUM * fmt.height);
146 		if (deltaE(colorbars[i], colorbars_target[i]) > LAB_THRESHOLD) {
147 			return false;
148 		}
149 	}
150 
151 	return true;
152 }
153 
154 #endif /* TEST_PATTERN_CHECK_H_ */
155