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