904 lines
32 KiB
C
904 lines
32 KiB
C
/**
|
|
* @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*/
|