项目场景:
lvgl在linux开发板下显示png图片不成功问题
问题描述
在linux环境下移植lvgl后测试显示png解码图片发现不会正常显示,其中"/app/out/BACK.png"为开发板中图片的存放路径
char *path = "/app/out/BACK.png";
lv_obj_t *backimg;
lv_obj_t *lable;
lv_png_init();
backimg = lv_img_create(lv_scr_act());
lv_img_set_src(backimg, path);
lv_obj_align_to(backimg,NULL,LV_ALIGN_CENTER,0,0);
原因分析:
进入lv_img_set_src函数当中,发现会进入到函数lv_img_decoder_get_info当中
lv_img_header_t header;
lv_img_decoder_get_info(src, &header);
在这个函数中会去遍历查找之前lv_png_init函数初始化过的解码器,遍历到后则会调用info_cb对应的解码器函数,这里是之前初始化的decoder_info函数
lv_res_t res = LV_RES_INV;
lv_img_decoder_t * d;
_LV_LL_READ(&LV_GC_ROOT(_lv_img_decoder_ll), d) {
if(d->info_cb) {
//printf("find info_cb\n");
res = d->info_cb(d, src, header);
if(res == LV_RES_OK) break;
}
}
void lv_png_init(void)
{
lv_img_decoder_t * dec = lv_img_decoder_create();
lv_img_decoder_set_info_cb(dec, decoder_info);
lv_img_decoder_set_open_cb(dec, decoder_open);
lv_img_decoder_set_close_cb(dec, decoder_close);
}
进入到decoder_info函数中,打印res结果发现res结果为LV_FS_RES_UNKNOWN
if(src_type == LV_IMG_SRC_FILE) {
const char * fn = src;
if(!strcmp(&fn[strlen(fn) - 3], "png")) { /*Check the extension*/
/* Read the width and height from the file. They have a constant location:
* [16..23]: width
* [24..27]: height
*/
uint32_t size[2];
lv_fs_file_t f;
lv_fs_res_t res = lv_fs_open(&f, fn, LV_FS_MODE_RD);printf("fun:png decoder_info,path=%s, res=%d\n",fn,res);
if(res != LV_FS_RES_OK) return LV_RES_INV;
lv_fs_seek(&f, 16, LV_FS_SEEK_SET);
uint32_t rn;
lv_fs_read(&f, &size, 8, &rn);
if(rn != 8) return LV_RES_INV;
lv_fs_close(&f);
/*Save the data in the header*/
header->always_zero = 0;
header->cf = LV_IMG_CF_RAW_ALPHA;
/*The width and height are stored in Big endian format so convert them to little endian*/
header->w = (lv_coord_t)((size[0] & 0xff000000) >> 24) + ((size[0] & 0x00ff0000) >> 8);
header->h = (lv_coord_t)((size[1] & 0xff000000) >> 24) + ((size[1] & 0x00ff0000) >> 8);
return LV_RES_OK;
}
}
继续进入lv_fs_open函数中查看,这里首先会去获取路径字符串的第一个字符用以匹配,我的letter设置如下,所以匹配的字符应该是“/”
char letter = path[0];
lv_fs_drv_t * drv = lv_fs_get_drv(letter);
lv_fs_drv_t * lv_fs_get_drv(char letter)
{
lv_fs_drv_t ** drv;
//printf("letter:%c\n",letter);
_LV_LL_READ(&LV_GC_ROOT(_lv_fsdrv_ll), drv) {
if((*drv)->letter == letter) {
return *drv;
}
}
return NULL;
}
#define LV_USE_FS_POSIX 1
#if LV_USE_FS_POSIX
#define LV_FS_POSIX_LETTER '/' /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/
#define LV_FS_POSIX_PATH "" /*Set the working directory. File/directory paths will be appended to it.*/
#define LV_FS_POSIX_CACHE_SIZE 0 /*>0 to cache this number of bytes in lv_fs_read()*/
#endif
匹配成功后会进入到open_cb函数中,这里对应的就是真正的open函数,注意这里get_real_path函数是个大坑
const char * real_path = lv_fs_get_real_path(path);
//printf("real path:%s\n",real_path);
void * file_d = drv->open_cb(drv, real_path, mode);
if(file_d == NULL || file_d == (void *)(-1)) {
return LV_FS_RES_UNKNOWN;
}
这里的drv->open_cb事先被注册过了,因为我所使用的文件系统是fs_posix,所以对应fs_posix的的open函数,具体的函数在lv_fs_posix.c文件当中。关于这个函数是什么时候注册的这里说明一下
首先前面提到的LV_USE_FS_POSIX宏打开后在lv_extra.c文件中会进行fs_posix的初始化工作,并在初始化中注册到lv_fs这一层
#if LV_USE_FS_POSIX != '\0'
lv_fs_posix_init();
#endif
void lv_fs_posix_init(void)
{
/*---------------------------------------------------
* Register the file system interface in LVGL
*--------------------------------------------------*/
/*Add a simple drive to open images*/
static lv_fs_drv_t fs_drv; /*A driver descriptor*/
lv_fs_drv_init(&fs_drv);
/*Set up fields...*/
fs_drv.letter = LV_FS_POSIX_LETTER;
fs_drv.cache_size = LV_FS_POSIX_CACHE_SIZE;
fs_drv.open_cb = fs_open;
fs_drv.close_cb = fs_close;
fs_drv.read_cb = fs_read;
fs_drv.write_cb = fs_write;
fs_drv.seek_cb = fs_seek;
fs_drv.tell_cb = fs_tell;
fs_drv.dir_close_cb = fs_dir_close;
fs_drv.dir_open_cb = fs_dir_open;
fs_drv.dir_read_cb = fs_dir_read;
lv_fs_drv_register(&fs_drv);
}
所以drv->open_cb函数对应这里面的fs_open函数。
进去后发现最后调用的确实是open函数
static void * fs_open(lv_fs_drv_t * drv, const char * path, lv_fs_mode_t mode)
{
LV_UNUSED(drv);
//printf("fs_open,path=%s,mode=%d\n",path,mode);
uint32_t flags = 0;
if(mode == LV_FS_MODE_WR) flags = O_WRONLY;
else if(mode == LV_FS_MODE_RD) flags = O_RDONLY;
else if(mode == (LV_FS_MODE_WR | LV_FS_MODE_RD)) flags = O_RDWR;
/*Make the path relative to the current directory (the projects root folder)*/
char buf[256];
sprintf(buf, LV_FS_POSIX_PATH "%s", path);
int f = open(buf, flags);//printf("open result:%d\n",f);
if(f < 0) return NULL;
return (void *)(lv_uintptr_t)f;
}
打印open函数的结果发现为负值(打开失败),然后检查文件路径是否正常,一检查果然发现问题了,在open函数中打印路径发现为“app/out/BACK.png”,明显和原来相比少了第一个字符,那到底是哪里出了问题导致少了字符呢?
继续往回翻找发现前面lv_fs_open函数中有一个get_real_path函数,这个函数是干嘛的?往里面一看发现不对劲了
const char * real_path = lv_fs_get_real_path(path);
//printf("real path:%s\n",real_path);
void * file_d = drv->open_cb(drv, real_path, mode);
if(file_d == NULL || file_d == (void *)(-1)) {
return LV_FS_RES_UNKNOWN;
}
static const char * lv_fs_get_real_path(const char * path)
{
//path++; /*Ignore the driver letter*/
if(*path == ':') path++;
return path;
}
在这个函数中会执行path++,这是在单片机文件系统或win文件系统中的措施,去掉前面的盘符字符,但linux这里是不需要的,所以导致将前面的“/”字符给去掉了。将这句注释后图片显示正常
解决方案:
将lv_fs_open函数中lv_fs_get_real_path函数中path++注释掉