/** * @file lv_sjpg.c * */ /*---------------------------------------------------------------------------------------------------------------------------------- / Added normal JPG support [7/10/2020] / ---------- / SJPEG is a custom created modified JPEG file format for small embedded platforms. / It will contain multiple JPEG fragments all embedded into a single file with a custom header. / This makes JPEG decoding easier using any JPEG library. Overall file size will be almost / similar to the parent jpeg file. We can generate sjpeg from any jpeg using a python script / provided along with this project. / (by vinodstanur | 2020 ) / SJPEG FILE STRUCTURE / -------------------------------------------------------------------------------------------------------------------------------- / Bytes | Value | / -------------------------------------------------------------------------------------------------------------------------------- / / 0 - 7 | "_SJPG__" followed by '\0' / / 8 - 13 | "V1.00" followed by '\0' [VERSION OF SJPG FILE for future compatibiliby] / / 14 - 15 | X_RESOLUTION (width) [little endian] / / 16 - 17 | Y_RESOLUTION (height) [little endian] / / 18 - 19 | TOTAL_FRAMES inside sjpeg [little endian] / / 20 - 21 | JPEG BLOCK WIDTH (16 normally) [little endian] / / 22 - [(TOTAL_FRAMES*2 )] | SIZE OF EACH JPEG SPLIT FRAGMENTS (FRAME_INFO_ARRAY) / / SJPEG data | Each JPEG frame can be extracted from SJPEG data by parsing the FRAME_INFO_ARRAY one time. / /---------------------------------------------------------------------------------------------------------------------------------- / JPEG DECODER / ------------ / We are using TJpgDec - Tiny JPEG Decompressor library from ELM-CHAN for decoding each split-jpeg fragments. / The tjpgd.c and tjpgd.h is not modified and those are used as it is. So if any update comes for the tiny-jpeg, / just replace those files with updated files. /---------------------------------------------------------------------------------------------------------------------------------*/ /********************* * INCLUDES *********************/ #include "../../../lvgl.h" #if LV_USE_SJPG #include "tjpgd.h" #include "lv_sjpg.h" #include "../../../misc/lv_fs.h" /********************* * DEFINES *********************/ #define TJPGD_WORKBUFF_SIZE 4096 //Recommended by TJPGD libray //NEVER EDIT THESE OFFSET VALUES #define SJPEG_VERSION_OFFSET 8 #define SJPEG_X_RES_OFFSET 14 #define SJPEG_y_RES_OFFSET 16 #define SJPEG_TOTAL_FRAMES_OFFSET 18 #define SJPEG_BLOCK_WIDTH_OFFSET 20 #define SJPEG_FRAME_INFO_ARRAY_OFFSET 22 /********************** * TYPEDEFS **********************/ enum io_source_type { SJPEG_IO_SOURCE_C_ARRAY, SJPEG_IO_SOURCE_DISK, }; typedef struct { enum io_source_type type; lv_fs_file_t lv_file; uint8_t * img_cache_buff; int img_cache_x_res; int img_cache_y_res; uint8_t * raw_sjpg_data; //Used when type==SJPEG_IO_SOURCE_C_ARRAY. uint32_t raw_sjpg_data_size; //Num bytes pointed to by raw_sjpg_data. uint32_t raw_sjpg_data_next_read_pos; //Used for all types. } io_source_t; typedef struct { uint8_t * sjpeg_data; uint32_t sjpeg_data_size; int sjpeg_x_res; int sjpeg_y_res; int sjpeg_total_frames; int sjpeg_single_frame_height; int sjpeg_cache_frame_index; uint8_t ** frame_base_array; //to save base address of each split frames upto sjpeg_total_frames. int * frame_base_offset; //to save base offset for fseek uint8_t * frame_cache; uint8_t * workb; //JPG work buffer for jpeg library JDEC * tjpeg_jd; io_source_t io; } SJPEG; /********************** * STATIC PROTOTYPES **********************/ static lv_res_t decoder_info(lv_img_decoder_t * decoder, const void * src, lv_img_header_t * header); static lv_res_t decoder_open(lv_img_decoder_t * decoder, lv_img_decoder_dsc_t * dsc); static lv_res_t decoder_read_line(lv_img_decoder_t * decoder, lv_img_decoder_dsc_t * dsc, lv_coord_t x, lv_coord_t y, lv_coord_t len, uint8_t * buf); static void decoder_close(lv_img_decoder_t * decoder, lv_img_decoder_dsc_t * dsc); static size_t input_func(JDEC * jd, uint8_t * buff, size_t ndata); static int is_jpg(const uint8_t * raw_data, size_t len); static void lv_sjpg_cleanup(SJPEG * sjpeg); static void lv_sjpg_free(SJPEG * sjpeg); /********************** * STATIC VARIABLES **********************/ /********************** * MACROS **********************/ /********************** * GLOBAL FUNCTIONS **********************/ void lv_split_jpeg_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); lv_img_decoder_set_read_line_cb(dec, decoder_read_line); } /********************** * STATIC FUNCTIONS **********************/ /** * Get info about an SJPG / JPG image * @param decoder pointer to the decoder where this function belongs * @param src can be file name or pointer to a C array * @param header store the info here * @return LV_RES_OK: no error; LV_RES_INV: can't get the info */ static lv_res_t decoder_info(lv_img_decoder_t * decoder, const void * src, lv_img_header_t * header) { LV_UNUSED(decoder); /*Check whether the type `src` is known by the decoder*/ /* Read the SJPG/JPG header and find `width` and `height` */ lv_img_src_t src_type = lv_img_src_get_type(src); /*Get the source type*/ lv_res_t ret = LV_RES_OK; if(src_type == LV_IMG_SRC_VARIABLE) { const lv_img_dsc_t * img_dsc = src; uint8_t * raw_sjpeg_data = (uint8_t *)img_dsc->data; const uint32_t raw_sjpeg_data_size = img_dsc->data_size; if(!strncmp((char *)raw_sjpeg_data, "_SJPG__", strlen("_SJPG__"))) { raw_sjpeg_data += 14; //seek to res info ... refer sjpeg format header->always_zero = 0; header->cf = LV_IMG_CF_RAW; header->w = *raw_sjpeg_data++; header->w |= *raw_sjpeg_data++ << 8; header->h = *raw_sjpeg_data++; header->h |= *raw_sjpeg_data++ << 8; return ret; } else if(is_jpg(raw_sjpeg_data, raw_sjpeg_data_size) == true) { header->always_zero = 0; header->cf = LV_IMG_CF_RAW; uint8_t * workb_temp = lv_mem_alloc(TJPGD_WORKBUFF_SIZE); if(!workb_temp) return LV_RES_INV; io_source_t io_source_temp; io_source_temp.type = SJPEG_IO_SOURCE_C_ARRAY; io_source_temp.raw_sjpg_data = raw_sjpeg_data; io_source_temp.raw_sjpg_data_size = raw_sjpeg_data_size; io_source_temp.raw_sjpg_data_next_read_pos = 0; JDEC jd_tmp; JRESULT rc = jd_prepare(&jd_tmp, input_func, workb_temp, (size_t)TJPGD_WORKBUFF_SIZE, &io_source_temp); if(rc == JDR_OK) { header->w = jd_tmp.width; header->h = jd_tmp.height; } else { ret = LV_RES_INV; goto end; } end: lv_mem_free(workb_temp); return ret; } } else if(src_type == LV_IMG_SRC_FILE) { const char * fn = src; if(strcmp(lv_fs_get_ext(fn), "sjpg") == 0) { uint8_t buff[22]; memset(buff, 0, sizeof(buff)); lv_fs_file_t file; lv_fs_res_t res = lv_fs_open(&file, fn, LV_FS_MODE_RD); if(res != LV_FS_RES_OK) return 78; uint32_t rn; res = lv_fs_read(&file, buff, 8, &rn); if(res != LV_FS_RES_OK || rn != 8) { lv_fs_close(&file); return LV_RES_INV; } if(strcmp((char *)buff, "_SJPG__") == 0) { lv_fs_seek(&file, 14, LV_FS_SEEK_SET); res = lv_fs_read(&file, buff, 4, &rn); if(res != LV_FS_RES_OK || rn != 4) { lv_fs_close(&file); return LV_RES_INV; } header->always_zero = 0; header->cf = LV_IMG_CF_RAW; uint8_t * raw_sjpeg_data = buff; header->w = *raw_sjpeg_data++; header->w |= *raw_sjpeg_data++ << 8; header->h = *raw_sjpeg_data++; header->h |= *raw_sjpeg_data++ << 8; lv_fs_close(&file); return LV_RES_OK; } } else if(strcmp(lv_fs_get_ext(fn), "jpg") == 0) { lv_fs_file_t file; lv_fs_res_t res = lv_fs_open(&file, fn, LV_FS_MODE_RD); if(res != LV_FS_RES_OK) return 78; uint8_t * workb_temp = lv_mem_alloc(TJPGD_WORKBUFF_SIZE); if(!workb_temp) { lv_fs_close(&file); return LV_RES_INV; } io_source_t io_source_temp; io_source_temp.type = SJPEG_IO_SOURCE_DISK; io_source_temp.raw_sjpg_data_next_read_pos = 0; io_source_temp.img_cache_buff = NULL; io_source_temp.lv_file = file; JDEC jd_tmp; JRESULT rc = jd_prepare(&jd_tmp, input_func, workb_temp, (size_t)TJPGD_WORKBUFF_SIZE, &io_source_temp); lv_mem_free(workb_temp); lv_fs_close(&file); if(rc == JDR_OK) { header->always_zero = 0; header->cf = LV_IMG_CF_RAW; header->w = jd_tmp.width; header->h = jd_tmp.height; return LV_RES_OK; } } } return LV_RES_INV; } static int img_data_cb(JDEC * jd, void * data, JRECT * rect) { io_source_t * io = jd->device; uint8_t * cache = io->img_cache_buff; const int xres = io->img_cache_x_res; uint8_t * buf = data; const int INPUT_PIXEL_SIZE = 3; const int row_width = rect->right - rect->left + 1; // Row width in pixels. const int row_size = row_width * INPUT_PIXEL_SIZE; // Row size (bytes). for(int y = rect->top; y <= rect->bottom; y++) { int row_offset = y * xres * INPUT_PIXEL_SIZE + rect->left * INPUT_PIXEL_SIZE; memcpy(cache + row_offset, buf, row_size); buf += row_size; } return 1; } static size_t input_func(JDEC * jd, uint8_t * buff, size_t ndata) { io_source_t * io = jd->device; if(!io) return 0; if(io->type == SJPEG_IO_SOURCE_C_ARRAY) { const uint32_t bytes_left = io->raw_sjpg_data_size - io->raw_sjpg_data_next_read_pos; const uint32_t to_read = ndata <= bytes_left ? (uint32_t)ndata : bytes_left; if(to_read == 0) return 0; if(buff) { memcpy(buff, io->raw_sjpg_data + io->raw_sjpg_data_next_read_pos, to_read); } io->raw_sjpg_data_next_read_pos += to_read; return to_read; } else if(io->type == SJPEG_IO_SOURCE_DISK) { lv_fs_file_t * lv_file_p = &(io->lv_file); if(buff) { uint32_t rn = 0; lv_fs_read(lv_file_p, buff, (uint32_t)ndata, &rn); return rn; } else { uint32_t pos; lv_fs_tell(lv_file_p, &pos); lv_fs_seek(lv_file_p, (uint32_t)(ndata + pos), LV_FS_SEEK_SET); return ndata; } } return 0; } /** * Open SJPG image and return the decided image * @param decoder pointer to the decoder where this function belongs * @param dsc pointer to a descriptor which describes this decoding session * @return LV_RES_OK: no error; LV_RES_INV: can't get the info */ static lv_res_t decoder_open(lv_img_decoder_t * decoder, lv_img_decoder_dsc_t * dsc) { LV_UNUSED(decoder); lv_res_t lv_ret = LV_RES_OK; if(dsc->src_type == LV_IMG_SRC_VARIABLE) { uint8_t * data; SJPEG * sjpeg = (SJPEG *) dsc->user_data; const uint32_t raw_sjpeg_data_size = ((lv_img_dsc_t *)dsc->src)->data_size; if(sjpeg == NULL) { sjpeg = lv_mem_alloc(sizeof(SJPEG)); if(!sjpeg) return LV_RES_INV; memset(sjpeg, 0, sizeof(SJPEG)); dsc->user_data = sjpeg; sjpeg->sjpeg_data = (uint8_t *)((lv_img_dsc_t *)(dsc->src))->data; sjpeg->sjpeg_data_size = ((lv_img_dsc_t *)(dsc->src))->data_size; } if(!strncmp((char *) sjpeg->sjpeg_data, "_SJPG__", strlen("_SJPG__"))) { data = sjpeg->sjpeg_data; data += 14; sjpeg->sjpeg_x_res = *data++; sjpeg->sjpeg_x_res |= *data++ << 8; sjpeg->sjpeg_y_res = *data++; sjpeg->sjpeg_y_res |= *data++ << 8; sjpeg->sjpeg_total_frames = *data++; sjpeg->sjpeg_total_frames |= *data++ << 8; sjpeg->sjpeg_single_frame_height = *data++; sjpeg->sjpeg_single_frame_height |= *data++ << 8; sjpeg->frame_base_array = lv_mem_alloc(sizeof(uint8_t *) * sjpeg->sjpeg_total_frames); if(! sjpeg->frame_base_array) { lv_sjpg_cleanup(sjpeg); sjpeg = NULL; return LV_RES_INV; } sjpeg->frame_base_offset = NULL; uint8_t * img_frame_base = data + sjpeg->sjpeg_total_frames * 2; sjpeg->frame_base_array[0] = img_frame_base; for(int i = 1; i < sjpeg->sjpeg_total_frames; i++) { int offset = *data++; offset |= *data++ << 8; sjpeg->frame_base_array[i] = sjpeg->frame_base_array[i - 1] + offset; } sjpeg->sjpeg_cache_frame_index = -1; sjpeg->frame_cache = (void *)lv_mem_alloc(sjpeg->sjpeg_x_res * sjpeg->sjpeg_single_frame_height * 3/*2*/); if(! sjpeg->frame_cache) { lv_sjpg_cleanup(sjpeg); sjpeg = NULL; return LV_RES_INV; } sjpeg->io.img_cache_buff = sjpeg->frame_cache; sjpeg->io.img_cache_x_res = sjpeg->sjpeg_x_res; sjpeg->workb = lv_mem_alloc(TJPGD_WORKBUFF_SIZE); if(! sjpeg->workb) { lv_sjpg_cleanup(sjpeg); sjpeg = NULL; return LV_RES_INV; } sjpeg->tjpeg_jd = lv_mem_alloc(sizeof(JDEC)); if(! sjpeg->tjpeg_jd) { lv_sjpg_cleanup(sjpeg); sjpeg = NULL; return LV_RES_INV; } sjpeg->io.type = SJPEG_IO_SOURCE_C_ARRAY; sjpeg->io.lv_file.file_d = NULL; dsc->img_data = NULL; return lv_ret; } else if(is_jpg(sjpeg->sjpeg_data, raw_sjpeg_data_size) == true) { uint8_t * workb_temp = lv_mem_alloc(TJPGD_WORKBUFF_SIZE); if(! workb_temp) { lv_sjpg_cleanup(sjpeg); sjpeg = NULL; return LV_RES_INV; } io_source_t io_source_temp; io_source_temp.type = SJPEG_IO_SOURCE_C_ARRAY; io_source_temp.raw_sjpg_data = sjpeg->sjpeg_data; io_source_temp.raw_sjpg_data_size = sjpeg->sjpeg_data_size; io_source_temp.raw_sjpg_data_next_read_pos = 0; JDEC jd_tmp; JRESULT rc = jd_prepare(&jd_tmp, input_func, workb_temp, (size_t)TJPGD_WORKBUFF_SIZE, &io_source_temp); lv_mem_free(workb_temp); if(rc == JDR_OK) { sjpeg->sjpeg_x_res = jd_tmp.width; sjpeg->sjpeg_y_res = jd_tmp.height; sjpeg->sjpeg_total_frames = 1; sjpeg->sjpeg_single_frame_height = jd_tmp.height; sjpeg->frame_base_array = lv_mem_alloc(sizeof(uint8_t *) * sjpeg->sjpeg_total_frames); if(! sjpeg->frame_base_array) { lv_sjpg_cleanup(sjpeg); sjpeg = NULL; return LV_RES_INV; } sjpeg->frame_base_offset = NULL; uint8_t * img_frame_base = sjpeg->sjpeg_data; sjpeg->frame_base_array[0] = img_frame_base; sjpeg->sjpeg_cache_frame_index = -1; sjpeg->frame_cache = (void *)lv_mem_alloc(sjpeg->sjpeg_x_res * sjpeg->sjpeg_single_frame_height * 3); if(! sjpeg->frame_cache) { lv_sjpg_cleanup(sjpeg); sjpeg = NULL; return LV_RES_INV; } sjpeg->io.img_cache_buff = sjpeg->frame_cache; sjpeg->io.img_cache_x_res = sjpeg->sjpeg_x_res; sjpeg->workb = lv_mem_alloc(TJPGD_WORKBUFF_SIZE); if(! sjpeg->workb) { lv_sjpg_cleanup(sjpeg); sjpeg = NULL; return LV_RES_INV; } sjpeg->tjpeg_jd = lv_mem_alloc(sizeof(JDEC)); if(! sjpeg->tjpeg_jd) { lv_sjpg_cleanup(sjpeg); sjpeg = NULL; return LV_RES_INV; } sjpeg->io.type = SJPEG_IO_SOURCE_C_ARRAY; sjpeg->io.lv_file.file_d = NULL; dsc->img_data = NULL; return lv_ret; } else { lv_ret = LV_RES_INV; goto end; } end: lv_mem_free(workb_temp); return lv_ret; } } else if(dsc->src_type == LV_IMG_SRC_FILE) { /* If all fine, then the file will be kept open */ const char * fn = dsc->src; uint8_t * data; if(strcmp(lv_fs_get_ext(fn), "sjpg") == 0) { uint8_t buff[22]; memset(buff, 0, sizeof(buff)); lv_fs_file_t lv_file; lv_fs_res_t res = lv_fs_open(&lv_file, fn, LV_FS_MODE_RD); if(res != LV_FS_RES_OK) { return 78; } uint32_t rn; res = lv_fs_read(&lv_file, buff, 22, &rn); if(res != LV_FS_RES_OK || rn != 22) { lv_fs_close(&lv_file); return LV_RES_INV; } if(strcmp((char *)buff, "_SJPG__") == 0) { SJPEG * sjpeg = (SJPEG *) dsc->user_data; if(sjpeg == NULL) { sjpeg = lv_mem_alloc(sizeof(SJPEG)); if(! sjpeg) { lv_fs_close(&lv_file); return LV_RES_INV; } memset(sjpeg, 0, sizeof(SJPEG)); dsc->user_data = sjpeg; sjpeg->sjpeg_data = (uint8_t *)((lv_img_dsc_t *)(dsc->src))->data; sjpeg->sjpeg_data_size = ((lv_img_dsc_t *)(dsc->src))->data_size; } data = buff; data += 14; sjpeg->sjpeg_x_res = *data++; sjpeg->sjpeg_x_res |= *data++ << 8; sjpeg->sjpeg_y_res = *data++; sjpeg->sjpeg_y_res |= *data++ << 8; sjpeg->sjpeg_total_frames = *data++; sjpeg->sjpeg_total_frames |= *data++ << 8; sjpeg->sjpeg_single_frame_height = *data++; sjpeg->sjpeg_single_frame_height |= *data++ << 8; sjpeg->frame_base_array = NULL;//lv_mem_alloc( sizeof(uint8_t *) * sjpeg->sjpeg_total_frames ); sjpeg->frame_base_offset = lv_mem_alloc(sizeof(int) * sjpeg->sjpeg_total_frames); if(! sjpeg->frame_base_offset) { lv_fs_close(&lv_file); lv_sjpg_cleanup(sjpeg); return LV_RES_INV; } int img_frame_start_offset = (SJPEG_FRAME_INFO_ARRAY_OFFSET + sjpeg->sjpeg_total_frames * 2); sjpeg->frame_base_offset[0] = img_frame_start_offset; //pointer used to save integer for now... for(int i = 1; i < sjpeg->sjpeg_total_frames; i++) { res = lv_fs_read(&lv_file, buff, 2, &rn); if(res != LV_FS_RES_OK || rn != 2) { lv_fs_close(&lv_file); return LV_RES_INV; } data = buff; int offset = *data++; offset |= *data++ << 8; sjpeg->frame_base_offset[i] = sjpeg->frame_base_offset[i - 1] + offset; } sjpeg->sjpeg_cache_frame_index = -1; //INVALID AT BEGINNING for a forced compare mismatch at first time. sjpeg->frame_cache = (void *)lv_mem_alloc(sjpeg->sjpeg_x_res * sjpeg->sjpeg_single_frame_height * 3); if(! sjpeg->frame_cache) { lv_fs_close(&lv_file); lv_sjpg_cleanup(sjpeg); return LV_RES_INV; } sjpeg->io.img_cache_buff = sjpeg->frame_cache; sjpeg->io.img_cache_x_res = sjpeg->sjpeg_x_res; sjpeg->workb = lv_mem_alloc(TJPGD_WORKBUFF_SIZE); if(! sjpeg->workb) { lv_fs_close(&lv_file); lv_sjpg_cleanup(sjpeg); return LV_RES_INV; } sjpeg->tjpeg_jd = lv_mem_alloc(sizeof(JDEC)); if(! sjpeg->tjpeg_jd) { lv_fs_close(&lv_file); lv_sjpg_cleanup(sjpeg); return LV_RES_INV; } sjpeg->io.type = SJPEG_IO_SOURCE_DISK; sjpeg->io.lv_file = lv_file; dsc->img_data = NULL; return LV_RES_OK; } } else if(strcmp(lv_fs_get_ext(fn), "jpg") == 0) { lv_fs_file_t lv_file; lv_fs_res_t res = lv_fs_open(&lv_file, fn, LV_FS_MODE_RD); if(res != LV_FS_RES_OK) { return LV_RES_INV; } SJPEG * sjpeg = (SJPEG *) dsc->user_data; if(sjpeg == NULL) { sjpeg = lv_mem_alloc(sizeof(SJPEG)); if(! sjpeg) { lv_fs_close(&lv_file); return LV_RES_INV; } memset(sjpeg, 0, sizeof(SJPEG)); dsc->user_data = sjpeg; sjpeg->sjpeg_data = (uint8_t *)((lv_img_dsc_t *)(dsc->src))->data; sjpeg->sjpeg_data_size = ((lv_img_dsc_t *)(dsc->src))->data_size; } uint8_t * workb_temp = lv_mem_alloc(TJPGD_WORKBUFF_SIZE); if(! workb_temp) { lv_fs_close(&lv_file); lv_sjpg_cleanup(sjpeg); return LV_RES_INV; } io_source_t io_source_temp; io_source_temp.type = SJPEG_IO_SOURCE_DISK; io_source_temp.raw_sjpg_data_next_read_pos = 0; io_source_temp.img_cache_buff = NULL; io_source_temp.lv_file = lv_file; JDEC jd_tmp; JRESULT rc = jd_prepare(&jd_tmp, input_func, workb_temp, (size_t)TJPGD_WORKBUFF_SIZE, &io_source_temp); lv_mem_free(workb_temp); if(rc == JDR_OK) { sjpeg->sjpeg_x_res = jd_tmp.width; sjpeg->sjpeg_y_res = jd_tmp.height; sjpeg->sjpeg_total_frames = 1; sjpeg->sjpeg_single_frame_height = jd_tmp.height; sjpeg->frame_base_array = NULL; sjpeg->frame_base_offset = lv_mem_alloc(sizeof(uint8_t *) * sjpeg->sjpeg_total_frames); if(! sjpeg->frame_base_offset) { lv_fs_close(&lv_file); lv_sjpg_cleanup(sjpeg); return LV_RES_INV; } int img_frame_start_offset = 0; sjpeg->frame_base_offset[0] = img_frame_start_offset; sjpeg->sjpeg_cache_frame_index = -1; sjpeg->frame_cache = (void *)lv_mem_alloc(sjpeg->sjpeg_x_res * sjpeg->sjpeg_single_frame_height * 3); if(! sjpeg->frame_cache) { lv_fs_close(&lv_file); lv_sjpg_cleanup(sjpeg); return LV_RES_INV; } sjpeg->io.img_cache_buff = sjpeg->frame_cache; sjpeg->io.img_cache_x_res = sjpeg->sjpeg_x_res; sjpeg->workb = lv_mem_alloc(TJPGD_WORKBUFF_SIZE); if(! sjpeg->workb) { lv_fs_close(&lv_file); lv_sjpg_cleanup(sjpeg); return LV_RES_INV; } sjpeg->tjpeg_jd = lv_mem_alloc(sizeof(JDEC)); if(! sjpeg->tjpeg_jd) { lv_fs_close(&lv_file); lv_sjpg_cleanup(sjpeg); return LV_RES_INV; } sjpeg->io.type = SJPEG_IO_SOURCE_DISK; sjpeg->io.lv_file = lv_file; dsc->img_data = NULL; return LV_RES_OK; } else { if(dsc->user_data) lv_mem_free(dsc->user_data); lv_fs_close(&lv_file); return LV_RES_INV; } } } return LV_RES_INV; } /** * Decode `len` pixels starting from the given `x`, `y` coordinates and store them in `buf`. * Required only if the "open" function can't open the whole decoded pixel array. (dsc->img_data == NULL) * @param decoder pointer to the decoder the function associated with * @param dsc pointer to decoder descriptor * @param x start x coordinate * @param y start y coordinate * @param len number of pixels to decode * @param buf a buffer to store the decoded pixels * @return LV_RES_OK: ok; LV_RES_INV: failed */ static lv_res_t decoder_read_line(lv_img_decoder_t * decoder, lv_img_decoder_dsc_t * dsc, lv_coord_t x, lv_coord_t y, lv_coord_t len, uint8_t * buf) { LV_UNUSED(decoder); if(dsc->src_type == LV_IMG_SRC_VARIABLE) { SJPEG * sjpeg = (SJPEG *) dsc->user_data; JRESULT rc; int sjpeg_req_frame_index = y / sjpeg->sjpeg_single_frame_height; /*If line not from cache, refresh cache */ if(sjpeg_req_frame_index != sjpeg->sjpeg_cache_frame_index) { sjpeg->io.raw_sjpg_data = sjpeg->frame_base_array[ sjpeg_req_frame_index ]; if(sjpeg_req_frame_index == (sjpeg->sjpeg_total_frames - 1)) { /*This is the last frame. */ const uint32_t frame_offset = (uint32_t)(sjpeg->io.raw_sjpg_data - sjpeg->sjpeg_data); sjpeg->io.raw_sjpg_data_size = sjpeg->sjpeg_data_size - frame_offset; } else { sjpeg->io.raw_sjpg_data_size = (uint32_t)(sjpeg->frame_base_array[sjpeg_req_frame_index + 1] - sjpeg->io.raw_sjpg_data); } sjpeg->io.raw_sjpg_data_next_read_pos = 0; rc = jd_prepare(sjpeg->tjpeg_jd, input_func, sjpeg->workb, (size_t)TJPGD_WORKBUFF_SIZE, &(sjpeg->io)); if(rc != JDR_OK) return LV_RES_INV; rc = jd_decomp(sjpeg->tjpeg_jd, img_data_cb, 0); if(rc != JDR_OK) return LV_RES_INV; sjpeg->sjpeg_cache_frame_index = sjpeg_req_frame_index; } int offset = 0; uint8_t * cache = (uint8_t *)sjpeg->frame_cache + x * 3 + (y % sjpeg->sjpeg_single_frame_height) * sjpeg->sjpeg_x_res * 3; #if LV_COLOR_DEPTH == 32 for(int i = 0; i < len; i++) { buf[offset + 3] = 0xff; buf[offset + 2] = *cache++; buf[offset + 1] = *cache++; buf[offset + 0] = *cache++; offset += 4; } #elif LV_COLOR_DEPTH == 16 for(int i = 0; i < len; i++) { uint16_t col_16bit = (*cache++ & 0xf8) << 8; col_16bit |= (*cache++ & 0xFC) << 3; col_16bit |= (*cache++ >> 3); #if LV_BIG_ENDIAN_SYSTEM == 1 || LV_COLOR_16_SWAP == 1 buf[offset++] = col_16bit >> 8; buf[offset++] = col_16bit & 0xff; #else buf[offset++] = col_16bit & 0xff; buf[offset++] = col_16bit >> 8; #endif // LV_BIG_ENDIAN_SYSTEM } #elif LV_COLOR_DEPTH == 8 for(int i = 0; i < len; i++) { uint8_t col_8bit = (*cache++ & 0xC0); col_8bit |= (*cache++ & 0xe0) >> 2; col_8bit |= (*cache++ & 0xe0) >> 5; buf[offset++] = col_8bit; } #else #error Unsupported LV_COLOR_DEPTH #endif // LV_COLOR_DEPTH return LV_RES_OK; } else if(dsc->src_type == LV_IMG_SRC_FILE) { SJPEG * sjpeg = (SJPEG *) dsc->user_data; JRESULT rc; int sjpeg_req_frame_index = y / sjpeg->sjpeg_single_frame_height; lv_fs_file_t * lv_file_p = &(sjpeg->io.lv_file); if(!lv_file_p) goto end; /*If line not from cache, refresh cache */ if(sjpeg_req_frame_index != sjpeg->sjpeg_cache_frame_index) { sjpeg->io.raw_sjpg_data_next_read_pos = (int)(sjpeg->frame_base_offset [ sjpeg_req_frame_index ]); lv_fs_seek(&(sjpeg->io.lv_file), sjpeg->io.raw_sjpg_data_next_read_pos, LV_FS_SEEK_SET); rc = jd_prepare(sjpeg->tjpeg_jd, input_func, sjpeg->workb, (size_t)TJPGD_WORKBUFF_SIZE, &(sjpeg->io)); if(rc != JDR_OK) return LV_RES_INV; rc = jd_decomp(sjpeg->tjpeg_jd, img_data_cb, 0); if(rc != JDR_OK) return LV_RES_INV; sjpeg->sjpeg_cache_frame_index = sjpeg_req_frame_index; } int offset = 0; uint8_t * cache = (uint8_t *)sjpeg->frame_cache + x * 3 + (y % sjpeg->sjpeg_single_frame_height) * sjpeg->sjpeg_x_res * 3; #if LV_COLOR_DEPTH == 32 for(int i = 0; i < len; i++) { buf[offset + 3] = 0xff; buf[offset + 2] = *cache++; buf[offset + 1] = *cache++; buf[offset + 0] = *cache++; offset += 4; } #elif LV_COLOR_DEPTH == 16 for(int i = 0; i < len; i++) { uint16_t col_8bit = (*cache++ & 0xf8) << 8; col_8bit |= (*cache++ & 0xFC) << 3; col_8bit |= (*cache++ >> 3); #if LV_BIG_ENDIAN_SYSTEM == 1 || LV_COLOR_16_SWAP == 1 buf[offset++] = col_8bit >> 8; buf[offset++] = col_8bit & 0xff; #else buf[offset++] = col_8bit & 0xff; buf[offset++] = col_8bit >> 8; #endif // LV_BIG_ENDIAN_SYSTEM } #elif LV_COLOR_DEPTH == 8 for(int i = 0; i < len; i++) { uint8_t col_8bit = (*cache++ & 0xC0); col_8bit |= (*cache++ & 0xe0) >> 2; col_8bit |= (*cache++ & 0xe0) >> 5; buf[offset++] = col_8bit; } #else #error Unsupported LV_COLOR_DEPTH #endif // LV_COLOR_DEPTH return LV_RES_OK; } end: return LV_RES_INV; } /** * Free the allocated resources * @param decoder pointer to the decoder where this function belongs * @param dsc pointer to a descriptor which describes this decoding session */ static void decoder_close(lv_img_decoder_t * decoder, lv_img_decoder_dsc_t * dsc) { LV_UNUSED(decoder); /*Free all allocated data*/ SJPEG * sjpeg = (SJPEG *) dsc->user_data; if(!sjpeg) return; switch(dsc->src_type) { case LV_IMG_SRC_FILE: if(sjpeg->io.lv_file.file_d) { lv_fs_close(&(sjpeg->io.lv_file)); } lv_sjpg_cleanup(sjpeg); break; case LV_IMG_SRC_VARIABLE: lv_sjpg_cleanup(sjpeg); break; default: ; } } static int is_jpg(const uint8_t * raw_data, size_t len) { const uint8_t jpg_signature[] = {0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10, 0x4A, 0x46, 0x49, 0x46}; if(len < sizeof(jpg_signature)) return false; return memcmp(jpg_signature, raw_data, sizeof(jpg_signature)) == 0; } static void lv_sjpg_free(SJPEG * sjpeg) { if(sjpeg->frame_cache) lv_mem_free(sjpeg->frame_cache); if(sjpeg->frame_base_array) lv_mem_free(sjpeg->frame_base_array); if(sjpeg->frame_base_offset) lv_mem_free(sjpeg->frame_base_offset); if(sjpeg->tjpeg_jd) lv_mem_free(sjpeg->tjpeg_jd); if(sjpeg->workb) lv_mem_free(sjpeg->workb); } static void lv_sjpg_cleanup(SJPEG * sjpeg) { if(! sjpeg) return; lv_sjpg_free(sjpeg); lv_mem_free(sjpeg); } #endif /*LV_USE_SJPG*/