1 /**
2 * @file lv_test_assert.c
3 *
4 * Copyright 2002-2010 Guillaume Cottenceau.
5 *
6 * This software may be freely redistributed under the terms
7 * of the X11 license.
8 *
9 */
10
11 /*********************
12 * INCLUDES
13 *********************/
14 #if LV_BUILD_TEST
15 #include "../lvgl.h"
16 #include <stdlib.h>
17 #include <stdio.h>
18 #include <string.h>
19 #include <stdarg.h>
20 #include <errno.h>
21 #include "unity.h"
22 #define PNG_DEBUG 3
23 #include <png.h>
24
25 #ifdef _WIN32
26 #include <direct.h>
27 #define mkdir(pathname, mode) _mkdir(pathname)
28 #define strtok_r strtok_s
29 #else
30 #include <sys/stat.h>
31 #endif
32
33 /*********************
34 * DEFINES
35 *********************/
36
37 #ifndef REF_IMGS_PATH
38 #define REF_IMGS_PATH "ref_imgs/"
39 #endif
40
41 #ifndef REF_IMG_TOLERANCE
42 #define REF_IMG_TOLERANCE 0
43 #endif
44
45 #define ERR_FILE_NOT_FOUND -1
46 #define ERR_PNG -2
47
48 /**********************
49 * TYPEDEFS
50 **********************/
51 typedef struct {
52 int width, height;
53 png_byte color_type;
54 png_byte bit_depth;
55
56 png_structp png_ptr;
57 png_infop info_ptr;
58 int number_of_passes;
59 png_bytep * row_pointers;
60 } png_image_t;
61
62 /**********************
63 * STATIC PROTOTYPES
64 **********************/
65 static bool screenshot_compare(const char * fn_ref, const char * mode, uint8_t tolerance);
66 static int read_png_file(png_image_t * p, const char * file_name);
67 static int write_png_file(void * raw_img, uint32_t width, uint32_t height, char * file_name);
68 static void png_release(png_image_t * p);
69 static void buf_to_xrgb8888(const lv_draw_buf_t * draw_buf, uint8_t * buf_out);
70 static void create_folders_if_needed(const char * path) ;
71
72 /**********************
73 * STATIC VARIABLES
74 **********************/
75
76 /**********************
77 * MACROS
78 **********************/
79
80 /**********************
81 * GLOBAL FUNCTIONS
82 **********************/
83
lv_test_assert_image_eq(const char * fn_ref)84 bool lv_test_assert_image_eq(const char * fn_ref)
85 {
86 bool pass;
87
88 lv_obj_t * scr = lv_screen_active();
89 lv_obj_invalidate(scr);
90
91 pass = screenshot_compare(fn_ref, "full refresh", REF_IMG_TOLERANCE);
92 if(!pass) return false;
93
94 //Software has minor rounding errors when not the whole image is updated
95 //so ignore stripe invalidation for now
96 // uint32_t i;
97 // for(i = 0; i < 800; i += 50 ) {
98 // lv_area_t a;
99 // a.y1 = 0;
100 // a.y2 = 479;
101 // a.x1 = i;
102 // a.x2 = i + 12;
103 // lv_obj_invalidate_area(scr, &a);
104 //
105 // a.x1 = i + 25;
106 // a.x2 = i + 32;
107 // lv_obj_invalidate_area(scr, &a);
108 // }
109 //
110 // pass = screenshot_compare(fn_ref, "vertical stripes", 32);
111 // if(!pass) return false;
112 //
113 //
114 // for(i = 0; i < 480; i += 40) {
115 // lv_area_t a;
116 // a.x1 = 0;
117 // a.x2 = 799;
118 // a.y1 = i;
119 // a.y2 = i + 9;
120 // lv_obj_invalidate_area(scr, &a);
121 //
122 // a.y1 = i + 25;
123 // a.y2 = i + 32;
124 // lv_obj_invalidate_area(scr, &a);
125 // }
126 //
127 // pass = screenshot_compare(fn_ref, "horizontal stripes", 32);
128 // if(!pass) return false;
129
130 return true;
131 }
132
133 /**********************
134 * STATIC FUNCTIONS
135 **********************/
136
137 static uint8_t screen_buf_xrgb8888[800 * 480 * 4];
138 /**
139 * Compare the content of the frame buffer with a reference image
140 * @param fn_ref reference image name
141 * @param mode arbitrary string to tell more about the compare
142 * @return true: test passed; false: test failed
143 */
screenshot_compare(const char * fn_ref,const char * mode,uint8_t tolerance)144 static bool screenshot_compare(const char * fn_ref, const char * mode, uint8_t tolerance)
145 {
146
147 char fn_ref_full[256];
148 lv_snprintf(fn_ref_full, sizeof(fn_ref_full), "%s%s", REF_IMGS_PATH, fn_ref);
149
150 create_folders_if_needed(fn_ref_full);
151
152 lv_refr_now(NULL);
153
154 lv_draw_buf_t * draw_buf = lv_display_get_buf_active(NULL);
155 buf_to_xrgb8888(draw_buf, screen_buf_xrgb8888);
156
157 png_image_t p;
158 int res = read_png_file(&p, fn_ref_full);
159 if(res == ERR_FILE_NOT_FOUND) {
160 TEST_PRINTF("%s%s", fn_ref_full, " was not found, creating is now from the rendered screen");
161 fflush(stderr);
162 write_png_file(screen_buf_xrgb8888, 800, 480, fn_ref_full);
163 return true;
164 }
165 else if(res == ERR_PNG) {
166 return false;
167 }
168
169 uint8_t * ptr_act = NULL;
170 const png_byte * ptr_ref = NULL;
171
172 bool err = false;
173 int x, y;
174 for(y = 0; y < p.height; y++) {
175 uint8_t * screen_buf_tmp = screen_buf_xrgb8888 + 800 * 4 * y;
176 png_byte * row = p.row_pointers[y];
177 for(x = 0; x < p.width; x++) {
178 ptr_ref = &(row[x * 3]);
179 ptr_act = screen_buf_tmp;
180
181 if(LV_ABS((int32_t) ptr_act[0] - (int32_t) ptr_ref[0]) > tolerance ||
182 LV_ABS((int32_t) ptr_act[1] - (int32_t) ptr_ref[1]) > tolerance ||
183 LV_ABS((int32_t) ptr_act[2] - (int32_t) ptr_ref[2]) > tolerance) {
184 uint32_t act_px = (ptr_act[2] << 16) + (ptr_act[1] << 8) + (ptr_act[0] << 0);
185 uint32_t ref_px = 0;
186 memcpy(&ref_px, ptr_ref, 3);
187 TEST_PRINTF("\nScreenshot compare error\n"
188 " - File: %s\n"
189 " - Mode: %s\n"
190 " - At x:%d, y:%d.\n"
191 " - Expected: %X\n"
192 " - Actual: %X\n"
193 " - Tolerance: %d",
194 fn_ref_full, mode, x, y, ref_px, act_px, tolerance);
195 fflush(stderr);
196 err = true;
197 break;
198 }
199 screen_buf_tmp += 4;
200 }
201 if(err) break;
202 }
203
204 if(err) {
205 char fn_ref_no_ext[128];
206 lv_strlcpy(fn_ref_no_ext, fn_ref, sizeof(fn_ref_no_ext));
207 fn_ref_no_ext[strlen(fn_ref_no_ext) - 4] = '\0';
208
209 char fn_err_full[256];
210 lv_snprintf(fn_err_full, sizeof(fn_err_full), "%s%s_err.png", REF_IMGS_PATH, fn_ref_no_ext);
211
212 write_png_file(screen_buf_xrgb8888, 800, 480, fn_err_full);
213 }
214
215 png_release(&p);
216
217 fflush(stdout);
218 return !err;
219
220 }
221
read_png_file(png_image_t * p,const char * file_name)222 static int read_png_file(png_image_t * p, const char * file_name)
223 {
224 char header[8]; // 8 is the maximum size that can be checked
225
226 /*open file and test for it being a png*/
227 FILE * fp = fopen(file_name, "rb");
228 if(!fp) {
229 TEST_PRINTF("[read_png_file %s] could not be opened for reading", file_name);
230 return ERR_FILE_NOT_FOUND;
231 }
232
233 size_t rcnt = fread(header, 1, 8, fp);
234 if(rcnt != 8 || png_sig_cmp((png_const_bytep)header, 0, 8)) {
235 TEST_PRINTF("[read_png_file %s] not recognized as a PNG file", file_name);
236 return ERR_PNG;
237 }
238
239 /*initialize stuff*/
240 p->png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
241
242 if(!p->png_ptr) {
243 TEST_PRINTF("[read_png_file %s] png_create_read_struct failed", file_name);
244 return ERR_PNG;
245 }
246
247 p->info_ptr = png_create_info_struct(p->png_ptr);
248 if(!p->info_ptr) {
249 TEST_PRINTF("[read_png_file %s] png_create_info_struct failed", file_name);
250 return ERR_PNG;
251 }
252 if(setjmp(png_jmpbuf(p->png_ptr))) {
253 TEST_PRINTF("[read_png_file %s] Error during init_io", file_name);
254 return ERR_PNG;
255 }
256 png_init_io(p->png_ptr, fp);
257 png_set_sig_bytes(p->png_ptr, 8);
258
259 png_read_info(p->png_ptr, p->info_ptr);
260
261 p->width = png_get_image_width(p->png_ptr, p->info_ptr);
262 p->height = png_get_image_height(p->png_ptr, p->info_ptr);
263 p->color_type = png_get_color_type(p->png_ptr, p->info_ptr);
264 p->bit_depth = png_get_bit_depth(p->png_ptr, p->info_ptr);
265
266 p->number_of_passes = png_set_interlace_handling(p->png_ptr);
267 png_read_update_info(p->png_ptr, p->info_ptr);
268
269 /*read file*/
270 if(setjmp(png_jmpbuf(p->png_ptr))) {
271 TEST_PRINTF("[read_png_file %s] Error during read_image", file_name);
272 return ERR_PNG;
273 }
274 p->row_pointers = (png_bytep *) malloc(sizeof(png_bytep) * p->height);
275
276 int y;
277 for(y = 0; y < p->height; y++)
278 p->row_pointers[y] = (png_byte *) malloc(png_get_rowbytes(p->png_ptr, p->info_ptr));
279
280 png_read_image(p->png_ptr, p->row_pointers);
281
282 fclose(fp);
283 return 0;
284 }
285
write_png_file(void * raw_img,uint32_t width,uint32_t height,char * file_name)286 static int write_png_file(void * raw_img, uint32_t width, uint32_t height, char * file_name)
287 {
288 png_structp png_ptr;
289 png_infop info_ptr;
290
291 /* create file */
292 FILE * fp = fopen(file_name, "wb");
293 if(!fp) {
294 printf("###### %s\n", file_name);
295 fflush(stdout);
296 TEST_PRINTF("[write_png_file %s] could not be opened for writing", file_name);
297 TEST_PRINTF("%s", file_name);
298 return -1;
299 }
300
301 /* initialize stuff */
302 png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
303
304 if(!png_ptr) {
305 TEST_PRINTF("[write_png_file %s] png_create_write_struct failed", file_name);
306 return -1;
307 }
308
309 info_ptr = png_create_info_struct(png_ptr);
310 if(!info_ptr) {
311 TEST_PRINTF("[write_png_file %s] png_create_info_struct failed", file_name);
312 return -1;
313 }
314
315 if(setjmp(png_jmpbuf(png_ptr))) {
316 TEST_PRINTF("[write_png_file %s] Error during init_io", file_name);
317 return -1;
318 }
319
320 png_init_io(png_ptr, fp);
321
322 /* write header */
323 if(setjmp(png_jmpbuf(png_ptr))) {
324 TEST_PRINTF("[write_png_file %s] Error during writing header", file_name);
325 return -1;
326 }
327
328 png_set_IHDR(png_ptr, info_ptr, width, height,
329 8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE,
330 PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
331
332 png_write_info(png_ptr, info_ptr);
333
334 /* write bytes */
335 if(setjmp(png_jmpbuf(png_ptr))) {
336 TEST_PRINTF("[write_png_file %s] Error during writing bytes", file_name);
337 return -1;
338 }
339
340 uint8_t * raw_img8 = (uint8_t *)raw_img;
341 png_bytep * row_pointers = (png_bytep *) malloc(sizeof(png_bytep) * height);
342 for(uint32_t y = 0; y < height; y++) {
343 row_pointers[y] = malloc(3 * width);
344 uint8_t * line = raw_img8 + y * width * 4;
345 for(uint32_t x = 0; x < width; x++) {
346 row_pointers[y][x * 3 + 0] = line[x * 4 + 0];
347 row_pointers[y][x * 3 + 1] = line[x * 4 + 1];
348 row_pointers[y][x * 3 + 2] = line[x * 4 + 2];
349 }
350 }
351 png_write_image(png_ptr, row_pointers);
352
353 /* end write */
354 if(setjmp(png_jmpbuf(png_ptr))) {
355 TEST_PRINTF("[write_png_file %s] Error during end of write", file_name);
356 return -1;
357 }
358 png_write_end(png_ptr, NULL);
359
360 /* cleanup heap allocation */
361 for(uint32_t y = 0; y < height; y++) free(row_pointers[y]);
362 free(row_pointers);
363
364 png_destroy_write_struct(&png_ptr, &info_ptr);
365
366 fclose(fp);
367 return 0;
368 }
369
png_release(png_image_t * p)370 static void png_release(png_image_t * p)
371 {
372 int y;
373 for(y = 0; y < p->height; y++) free(p->row_pointers[y]);
374
375 free(p->row_pointers);
376
377 png_destroy_read_struct(&p->png_ptr, &p->info_ptr, NULL);
378 }
379
buf_to_xrgb8888(const lv_draw_buf_t * draw_buf,uint8_t * buf_out)380 static void buf_to_xrgb8888(const lv_draw_buf_t * draw_buf, uint8_t * buf_out)
381 {
382 uint32_t stride = draw_buf->header.stride;
383 lv_color_format_t cf_in = draw_buf->header.cf;
384 const uint8_t * buf_in = draw_buf->data;
385
386 if(cf_in == LV_COLOR_FORMAT_RGB565) {
387 uint32_t y;
388 for(y = 0; y < 480; y++) {
389
390 uint32_t x;
391 for(x = 0; x < 800; x++) {
392 const lv_color16_t * c16 = (const lv_color16_t *)&buf_in[x * 2];
393
394 buf_out[x * 4 + 3] = 0xff;
395 buf_out[x * 4 + 2] = (c16->blue * 2106) >> 8; /*To make it rounded*/
396 buf_out[x * 4 + 1] = (c16->green * 1037) >> 8;
397 buf_out[x * 4 + 0] = (c16->red * 2106) >> 8;
398 }
399
400 buf_in += stride;
401 buf_out += 800 * 4;
402 }
403 }
404 else if(cf_in == LV_COLOR_FORMAT_ARGB8888 || cf_in == LV_COLOR_FORMAT_XRGB8888) {
405 uint32_t y;
406 for(y = 0; y < 480; y++) {
407 uint32_t x;
408 for(x = 0; x < 800; x++) {
409 buf_out[x * 4 + 3] = buf_in[x * 4 + 3];
410 buf_out[x * 4 + 2] = buf_in[x * 4 + 0];
411 buf_out[x * 4 + 1] = buf_in[x * 4 + 1];
412 buf_out[x * 4 + 0] = buf_in[x * 4 + 2];
413 }
414
415 buf_in += stride;
416 buf_out += 800 * 4;
417 }
418 }
419 else if(cf_in == LV_COLOR_FORMAT_RGB888) {
420 uint32_t y;
421 for(y = 0; y < 480; y++) {
422 uint32_t x;
423 for(x = 0; x < 800; x++) {
424 buf_out[x * 4 + 3] = 0xff;
425 buf_out[x * 4 + 2] = buf_in[x * 3 + 0];
426 buf_out[x * 4 + 1] = buf_in[x * 3 + 1];
427 buf_out[x * 4 + 0] = buf_in[x * 3 + 2];
428 }
429
430 buf_in += stride;
431 buf_out += 800 * 4;
432 }
433 }
434 else if(cf_in == LV_COLOR_FORMAT_L8) {
435 uint32_t y;
436 for(y = 0; y < 480; y++) {
437 uint32_t x;
438 for(x = 0; x < 800; x++) {
439 buf_out[x * 4 + 3] = 0xff;
440 buf_out[x * 4 + 2] = buf_in[x];
441 buf_out[x * 4 + 1] = buf_in[x];
442 buf_out[x * 4 + 0] = buf_in[x];
443 }
444
445 buf_in += stride;
446 buf_out += 800 * 4;
447 }
448 }
449 else if(cf_in == LV_COLOR_FORMAT_AL88) {
450 uint32_t y;
451 for(y = 0; y < 480; y++) {
452 uint32_t x;
453 for(x = 0; x < 800; x++) {
454 buf_out[x * 4 + 3] = buf_in[x * 2 + 1];
455 buf_out[x * 4 + 2] = buf_in[x * 2 + 0];
456 buf_out[x * 4 + 1] = buf_in[x * 2 + 0];
457 buf_out[x * 4 + 0] = buf_in[x * 2 + 0];
458 }
459
460 buf_in += stride;
461 buf_out += 800 * 4;
462 }
463 }
464 else if(cf_in == LV_COLOR_FORMAT_I1) {
465 uint32_t y;
466 for(y = 0; y < 480; y++) {
467 uint32_t x;
468 for(x = 0; x < 800; x++) {
469 const uint8_t byte = buf_in[x / 8];
470 const uint8_t bit_pos = x % 8;
471 const uint8_t pixel = (byte >> (7 - bit_pos)) & 0x01;
472
473 buf_out[x * 4 + 3] = 0xff;
474 buf_out[x * 4 + 2] = pixel ? 0xff : 0x00;
475 buf_out[x * 4 + 1] = pixel ? 0xff : 0x00;
476 buf_out[x * 4 + 0] = pixel ? 0xff : 0x00;
477 }
478
479 buf_in += stride;
480 buf_out += 800 * 4;
481 }
482 }
483 }
484
create_folders_if_needed(const char * path)485 static void create_folders_if_needed(const char * path)
486 {
487 char * ptr;
488 char * pathCopy = strdup(path);
489 if(pathCopy == NULL) {
490 perror("Error duplicating path");
491 exit(EXIT_FAILURE);
492 }
493
494 char * token = strtok_r(pathCopy, "/", &ptr);
495 char current_path[1024] = {'\0'}; // Adjust the size as needed
496
497 while(token && ptr && *ptr != '\0') {
498 strcat(current_path, token);
499 strcat(current_path, "/");
500
501 int mkdir_retval = mkdir(current_path, 0777);
502 if(mkdir_retval == 0) {
503 printf("Created folder: %s\n", current_path);
504 }
505 else if(errno != EEXIST) {
506 perror("Error creating folder");
507 free(pathCopy);
508 exit(EXIT_FAILURE);
509 }
510
511 token = strtok_r(NULL, "/", &ptr);
512 }
513
514 free(pathCopy);
515 }
516
517 #endif
518