#include "gifdec.h" #include #include #include #include #define TAG "GIF" #define MIN(A, B) ((A) < (B) ? (A) : (B)) #define MAX(A, B) ((A) > (B) ? (A) : (B)) typedef struct Entry { uint16_t length; uint16_t prefix; uint8_t suffix; } Entry; typedef struct Table { int bulk; int nentries; Entry * entries; } Table; #if LV_GIF_CACHE_DECODE_DATA #define LZW_MAXBITS 12 #define LZW_TABLE_SIZE (1 << LZW_MAXBITS) #define LZW_CACHE_SIZE (LZW_TABLE_SIZE * 4) #endif static gd_GIF * gif_open(gd_GIF * gif); static bool f_gif_open(gd_GIF * gif, const void * path, bool is_file); static void f_gif_read(gd_GIF * gif, void * buf, size_t len); static int f_gif_seek(gd_GIF * gif, size_t pos, int k); static void f_gif_close(gd_GIF * gif); #if LV_USE_DRAW_SW_ASM == LV_DRAW_SW_ASM_HELIUM #include "gifdec_mve.h" #endif static uint16_t read_num(gd_GIF * gif) { uint8_t bytes[2]; f_gif_read(gif, bytes, 2); return bytes[0] + (((uint16_t) bytes[1]) << 8); } gd_GIF * gd_open_gif_file(const char * fname) { gd_GIF gif_base; memset(&gif_base, 0, sizeof(gif_base)); bool res = f_gif_open(&gif_base, fname, true); if(!res) return NULL; return gif_open(&gif_base); } gd_GIF * gd_open_gif_data(const void * data) { gd_GIF gif_base; memset(&gif_base, 0, sizeof(gif_base)); bool res = f_gif_open(&gif_base, data, false); if(!res) return NULL; return gif_open(&gif_base); } static gd_GIF * gif_open(gd_GIF * gif_base) { uint8_t sigver[3]; uint16_t width, height, depth; uint8_t fdsz, bgidx, aspect; uint8_t * bgcolor; int gct_sz; gd_GIF * gif = NULL; /* Header */ f_gif_read(gif_base, sigver, 3); if(memcmp(sigver, "GIF", 3) != 0) { ESP_LOGW(TAG, "invalid signature"); goto fail; } /* Version */ f_gif_read(gif_base, sigver, 3); if(memcmp(sigver, "89a", 3) != 0 && memcmp(sigver, "87a", 3) != 0) { ESP_LOGW(TAG, "invalid version"); goto fail; } /* Width x Height */ width = read_num(gif_base); height = read_num(gif_base); /* FDSZ */ f_gif_read(gif_base, &fdsz, 1); /* Presence of GCT */ if(!(fdsz & 0x80)) { ESP_LOGW(TAG, "no global color table"); goto fail; } /* Color Space's Depth */ depth = ((fdsz >> 4) & 7) + 1; /* Ignore Sort Flag. */ /* GCT Size */ gct_sz = 1 << ((fdsz & 0x07) + 1); /* Background Color Index */ f_gif_read(gif_base, &bgidx, 1); /* Aspect Ratio */ f_gif_read(gif_base, &aspect, 1); /* Create gd_GIF Structure. */ if(0 == width || 0 == height){ ESP_LOGW(TAG, "Zero size image"); goto fail; } #if LV_GIF_CACHE_DECODE_DATA if(0 == (INT_MAX - sizeof(gd_GIF) - LZW_CACHE_SIZE) / width / height / 5){ ESP_LOGW(TAG, "Image dimensions are too large"); goto fail; } gif = lv_malloc(sizeof(gd_GIF) + 5 * width * height + LZW_CACHE_SIZE); #else if(0 == (INT_MAX - sizeof(gd_GIF)) / width / height / 5){ ESP_LOGW(TAG, "Image dimensions are too large"); goto fail; } gif = lv_malloc(sizeof(gd_GIF) + 5 * width * height); #endif if(!gif) goto fail; memcpy(gif, gif_base, sizeof(gd_GIF)); gif->width = width; gif->height = height; gif->depth = depth; /* Read GCT */ gif->gct.size = gct_sz; f_gif_read(gif, gif->gct.colors, 3 * gif->gct.size); gif->palette = &gif->gct; gif->bgindex = bgidx; gif->canvas = (uint8_t *) &gif[1]; gif->frame = &gif->canvas[4 * width * height]; if(gif->bgindex) { memset(gif->frame, gif->bgindex, gif->width * gif->height); } bgcolor = &gif->palette->colors[gif->bgindex * 3]; #if LV_GIF_CACHE_DECODE_DATA gif->lzw_cache = gif->frame + width * height; #endif #ifdef GIFDEC_FILL_BG GIFDEC_FILL_BG(gif->canvas, gif->width * gif->height, 1, gif->width * gif->height, bgcolor, 0x00); #else for(int i = 0; i < gif->width * gif->height; i++) { gif->canvas[i * 4 + 0] = *(bgcolor + 2); gif->canvas[i * 4 + 1] = *(bgcolor + 1); gif->canvas[i * 4 + 2] = *(bgcolor + 0); gif->canvas[i * 4 + 3] = 0x00; // 初始化为透明,让第一帧根据自己的透明度设置来渲染 } #endif gif->anim_start = f_gif_seek(gif, 0, LV_FS_SEEK_CUR); gif->loop_count = -1; goto ok; fail: f_gif_close(gif_base); ok: return gif; } static void discard_sub_blocks(gd_GIF * gif) { uint8_t size; do { f_gif_read(gif, &size, 1); f_gif_seek(gif, size, LV_FS_SEEK_CUR); } while(size); } static void read_plain_text_ext(gd_GIF * gif) { if(gif->plain_text) { uint16_t tx, ty, tw, th; uint8_t cw, ch, fg, bg; size_t sub_block; f_gif_seek(gif, 1, LV_FS_SEEK_CUR); /* block size = 12 */ tx = read_num(gif); ty = read_num(gif); tw = read_num(gif); th = read_num(gif); f_gif_read(gif, &cw, 1); f_gif_read(gif, &ch, 1); f_gif_read(gif, &fg, 1); f_gif_read(gif, &bg, 1); sub_block = f_gif_seek(gif, 0, LV_FS_SEEK_CUR); gif->plain_text(gif, tx, ty, tw, th, cw, ch, fg, bg); f_gif_seek(gif, sub_block, LV_FS_SEEK_SET); } else { /* Discard plain text metadata. */ f_gif_seek(gif, 13, LV_FS_SEEK_CUR); } /* Discard plain text sub-blocks. */ discard_sub_blocks(gif); } static void read_graphic_control_ext(gd_GIF * gif) { uint8_t rdit; /* Discard block size (always 0x04). */ f_gif_seek(gif, 1, LV_FS_SEEK_CUR); f_gif_read(gif, &rdit, 1); gif->gce.disposal = (rdit >> 2) & 3; gif->gce.input = rdit & 2; gif->gce.transparency = rdit & 1; gif->gce.delay = read_num(gif); f_gif_read(gif, &gif->gce.tindex, 1); /* Skip block terminator. */ f_gif_seek(gif, 1, LV_FS_SEEK_CUR); } static void read_comment_ext(gd_GIF * gif) { if(gif->comment) { size_t sub_block = f_gif_seek(gif, 0, LV_FS_SEEK_CUR); gif->comment(gif); f_gif_seek(gif, sub_block, LV_FS_SEEK_SET); } /* Discard comment sub-blocks. */ discard_sub_blocks(gif); } static void read_application_ext(gd_GIF * gif) { char app_id[8]; char app_auth_code[3]; uint16_t loop_count; /* Discard block size (always 0x0B). */ f_gif_seek(gif, 1, LV_FS_SEEK_CUR); /* Application Identifier. */ f_gif_read(gif, app_id, 8); /* Application Authentication Code. */ f_gif_read(gif, app_auth_code, 3); if(!strncmp(app_id, "NETSCAPE", sizeof(app_id))) { /* Discard block size (0x03) and constant byte (0x01). */ f_gif_seek(gif, 2, LV_FS_SEEK_CUR); loop_count = read_num(gif); if(gif->loop_count < 0) { if(loop_count == 0) { gif->loop_count = 0; } else { gif->loop_count = loop_count + 1; } } /* Skip block terminator. */ f_gif_seek(gif, 1, LV_FS_SEEK_CUR); } else if(gif->application) { size_t sub_block = f_gif_seek(gif, 0, LV_FS_SEEK_CUR); gif->application(gif, app_id, app_auth_code); f_gif_seek(gif, sub_block, LV_FS_SEEK_SET); discard_sub_blocks(gif); } else { discard_sub_blocks(gif); } } static void read_ext(gd_GIF * gif) { uint8_t label; f_gif_read(gif, &label, 1); switch(label) { case 0x01: read_plain_text_ext(gif); break; case 0xF9: read_graphic_control_ext(gif); break; case 0xFE: read_comment_ext(gif); break; case 0xFF: read_application_ext(gif); break; default: ESP_LOGW(TAG, "unknown extension: %02X\n", label); } } static uint16_t get_key(gd_GIF *gif, int key_size, uint8_t *sub_len, uint8_t *shift, uint8_t *byte) { int bits_read; int rpad; int frag_size; uint16_t key; key = 0; for (bits_read = 0; bits_read < key_size; bits_read += frag_size) { rpad = (*shift + bits_read) % 8; if (rpad == 0) { /* Update byte. */ if (*sub_len == 0) { f_gif_read(gif, sub_len, 1); /* Must be nonzero! */ if (*sub_len == 0) return 0x1000; } f_gif_read(gif, byte, 1); (*sub_len)--; } frag_size = MIN(key_size - bits_read, 8 - rpad); key |= ((uint16_t) ((*byte) >> rpad)) << bits_read; } /* Clear extra bits to the left. */ key &= (1 << key_size) - 1; *shift = (*shift + key_size) % 8; return key; } #if LV_GIF_CACHE_DECODE_DATA /* Decompress image pixels. * Return 0 on success or -1 on out-of-memory (w.r.t. LZW code table) or parse error. */ static int read_image_data(gd_GIF *gif, int interlace) { uint8_t sub_len, shift, byte; int ret = 0; int key_size; int y, pass, linesize; uint8_t *ptr = NULL; uint8_t *ptr_row_start = NULL; uint8_t *ptr_base = NULL; size_t start, end; uint16_t key, clear_code, stop_code, curr_code; int frm_off, frm_size,curr_size,top_slot,new_codes,slot; /* The first value of the value sequence corresponding to key */ int first_value; int last_key; uint8_t *sp = NULL; uint8_t *p_stack = NULL; uint8_t *p_suffix = NULL; uint16_t *p_prefix = NULL; /* get initial key size and clear code, stop code */ f_gif_read(gif, &byte, 1); key_size = (int) byte; clear_code = 1 << key_size; stop_code = clear_code + 1; key = 0; start = f_gif_seek(gif, 0, LV_FS_SEEK_CUR); discard_sub_blocks(gif); end = f_gif_seek(gif, 0, LV_FS_SEEK_CUR); f_gif_seek(gif, start, LV_FS_SEEK_SET); linesize = gif->width; ptr_base = &gif->frame[gif->fy * linesize + gif->fx]; ptr_row_start = ptr_base; ptr = ptr_row_start; sub_len = shift = 0; /* decoder */ pass = 0; y = 0; p_stack = gif->lzw_cache; p_suffix = gif->lzw_cache + LZW_TABLE_SIZE; p_prefix = (uint16_t*)(gif->lzw_cache + LZW_TABLE_SIZE * 2); frm_off = 0; frm_size = gif->fw * gif->fh; curr_size = key_size + 1; top_slot = 1 << curr_size; new_codes = clear_code + 2; slot = new_codes; first_value = -1; last_key = -1; sp = p_stack; while (frm_off < frm_size) { /* copy data to frame buffer */ while (sp > p_stack) { if(frm_off >= frm_size){ ESP_LOGW(TAG, "LZW table token overflows the frame buffer"); return -1; } *ptr++ = *(--sp); frm_off += 1; /* read one line */ if ((ptr - ptr_row_start) == gif->fw) { if (interlace) { switch(pass) { case 0: case 1: y += 8; ptr_row_start += linesize * 8; break; case 2: y += 4; ptr_row_start += linesize * 4; break; case 3: y += 2; ptr_row_start += linesize * 2; break; default: break; } while (y >= gif->fh) { y = 4 >> pass; ptr_row_start = ptr_base + linesize * y; pass++; } } else { ptr_row_start += linesize; } ptr = ptr_row_start; } } key = get_key(gif, curr_size, &sub_len, &shift, &byte); if (key == stop_code || key >= LZW_TABLE_SIZE) break; if (key == clear_code) { curr_size = key_size + 1; slot = new_codes; top_slot = 1 << curr_size; first_value = last_key = -1; sp = p_stack; continue; } curr_code = key; /* * If the current code is a code that will be added to the decoding * dictionary, it is composed of the data list corresponding to the * previous key and its first data. * */ if (curr_code == slot && first_value >= 0) { *sp++ = first_value; curr_code = last_key; }else if(curr_code >= slot) break; while (curr_code >= new_codes) { *sp++ = p_suffix[curr_code]; curr_code = p_prefix[curr_code]; } *sp++ = curr_code; /* Add code to decoding dictionary */ if (slot < top_slot && last_key >= 0) { p_suffix[slot] = curr_code; p_prefix[slot++] = last_key; } first_value = curr_code; last_key = key; if (slot >= top_slot) { if (curr_size < LZW_MAXBITS) { top_slot <<= 1; curr_size += 1; } } } if (key == stop_code) f_gif_read(gif, &sub_len, 1); /* Must be zero! */ f_gif_seek(gif, end, LV_FS_SEEK_SET); return ret; } #else static Table * new_table(int key_size) { int key; int init_bulk = MAX(1 << (key_size + 1), 0x100); Table * table = lv_malloc(sizeof(*table) + sizeof(Entry) * init_bulk); if(table) { table->bulk = init_bulk; table->nentries = (1 << key_size) + 2; table->entries = (Entry *) &table[1]; for(key = 0; key < (1 << key_size); key++) table->entries[key] = (Entry) { 1, 0xFFF, key }; } return table; } /* Add table entry. Return value: * 0 on success * +1 if key size must be incremented after this addition * -1 if could not realloc table */ static int add_entry(Table ** tablep, uint16_t length, uint16_t prefix, uint8_t suffix) { Table * table = *tablep; if(table->nentries == table->bulk) { table->bulk *= 2; table = lv_realloc(table, sizeof(*table) + sizeof(Entry) * table->bulk); if(!table) return -1; table->entries = (Entry *) &table[1]; *tablep = table; } table->entries[table->nentries] = (Entry) { length, prefix, suffix }; table->nentries++; if((table->nentries & (table->nentries - 1)) == 0) return 1; return 0; } /* Compute output index of y-th input line, in frame of height h. */ static int interlaced_line_index(int h, int y) { int p; /* number of lines in current pass */ p = (h - 1) / 8 + 1; if(y < p) /* pass 1 */ return y * 8; y -= p; p = (h - 5) / 8 + 1; if(y < p) /* pass 2 */ return y * 8 + 4; y -= p; p = (h - 3) / 4 + 1; if(y < p) /* pass 3 */ return y * 4 + 2; y -= p; /* pass 4 */ return y * 2 + 1; } /* Decompress image pixels. * Return 0 on success or -1 on out-of-memory (w.r.t. LZW code table) or parse error. */ static int read_image_data(gd_GIF * gif, int interlace) { uint8_t sub_len, shift, byte; int init_key_size, key_size, table_is_full = 0; int frm_off, frm_size, str_len = 0, i, p, x, y; uint16_t key, clear, stop; int ret; Table * table; Entry entry = {0}; size_t start, end; f_gif_read(gif, &byte, 1); key_size = (int) byte; start = f_gif_seek(gif, 0, LV_FS_SEEK_CUR); discard_sub_blocks(gif); end = f_gif_seek(gif, 0, LV_FS_SEEK_CUR); f_gif_seek(gif, start, LV_FS_SEEK_SET); clear = 1 << key_size; stop = clear + 1; table = new_table(key_size); key_size++; init_key_size = key_size; sub_len = shift = 0; key = get_key(gif, key_size, &sub_len, &shift, &byte); /* clear code */ frm_off = 0; ret = 0; frm_size = gif->fw * gif->fh; while(frm_off < frm_size) { if(key == clear) { key_size = init_key_size; table->nentries = (1 << (key_size - 1)) + 2; table_is_full = 0; } else if(!table_is_full) { ret = add_entry(&table, str_len + 1, key, entry.suffix); if(ret == -1) { lv_free(table); return -1; } if(table->nentries == 0x1000) { ret = 0; table_is_full = 1; } } key = get_key(gif, key_size, &sub_len, &shift, &byte); if(key == clear) continue; if(key == stop || key == 0x1000) break; if(ret == 1) key_size++; entry = table->entries[key]; str_len = entry.length; if(frm_off + str_len > frm_size){ ESP_LOGW(TAG, "LZW table token overflows the frame buffer"); lv_free(table); return -1; } for(i = 0; i < str_len; i++) { p = frm_off + entry.length - 1; x = p % gif->fw; y = p / gif->fw; if(interlace) y = interlaced_line_index((int) gif->fh, y); gif->frame[(gif->fy + y) * gif->width + gif->fx + x] = entry.suffix; if(entry.prefix == 0xFFF) break; else entry = table->entries[entry.prefix]; } frm_off += str_len; if(key < table->nentries - 1 && !table_is_full) table->entries[table->nentries - 1].suffix = entry.suffix; } lv_free(table); if(key == stop) f_gif_read(gif, &sub_len, 1); /* Must be zero! */ f_gif_seek(gif, end, LV_FS_SEEK_SET); return 0; } #endif /* Read image. * Return 0 on success or -1 on out-of-memory (w.r.t. LZW code table) or parse error. */ static int read_image(gd_GIF * gif) { uint8_t fisrz; int interlace; /* Image Descriptor. */ gif->fx = read_num(gif); gif->fy = read_num(gif); gif->fw = read_num(gif); gif->fh = read_num(gif); if(gif->fx + (uint32_t)gif->fw > gif->width || gif->fy + (uint32_t)gif->fh > gif->height){ ESP_LOGW(TAG, "Frame coordinates out of image bounds"); return -1; } f_gif_read(gif, &fisrz, 1); interlace = fisrz & 0x40; /* Ignore Sort Flag. */ /* Local Color Table? */ if(fisrz & 0x80) { /* Read LCT */ gif->lct.size = 1 << ((fisrz & 0x07) + 1); f_gif_read(gif, gif->lct.colors, 3 * gif->lct.size); gif->palette = &gif->lct; } else gif->palette = &gif->gct; /* Image Data. */ return read_image_data(gif, interlace); } static void render_frame_rect(gd_GIF * gif, uint8_t * buffer) { int i = gif->fy * gif->width + gif->fx; #ifdef GIFDEC_RENDER_FRAME GIFDEC_RENDER_FRAME(&buffer[i * 4], gif->fw, gif->fh, gif->width, &gif->frame[i], gif->palette->colors, gif->gce.transparency ? gif->gce.tindex : 0x100); #else int j, k; uint8_t index, * color; for(j = 0; j < gif->fh; j++) { for(k = 0; k < gif->fw; k++) { index = gif->frame[(gif->fy + j) * gif->width + gif->fx + k]; color = &gif->palette->colors[index * 3]; if(!gif->gce.transparency || index != gif->gce.tindex) { buffer[(i + k) * 4 + 0] = *(color + 2); buffer[(i + k) * 4 + 1] = *(color + 1); buffer[(i + k) * 4 + 2] = *(color + 0); buffer[(i + k) * 4 + 3] = 0xFF; } } i += gif->width; } #endif } static void dispose(gd_GIF * gif) { int i; uint8_t * bgcolor; switch(gif->gce.disposal) { case 2: /* Restore to background color. */ bgcolor = &gif->palette->colors[gif->bgindex * 3]; uint8_t opa = 0xff; if(gif->gce.transparency) opa = 0x00; i = gif->fy * gif->width + gif->fx; #ifdef GIFDEC_FILL_BG GIFDEC_FILL_BG(&(gif->canvas[i * 4]), gif->fw, gif->fh, gif->width, bgcolor, opa); #else int j, k; for(j = 0; j < gif->fh; j++) { for(k = 0; k < gif->fw; k++) { gif->canvas[(i + k) * 4 + 0] = *(bgcolor + 2); gif->canvas[(i + k) * 4 + 1] = *(bgcolor + 1); gif->canvas[(i + k) * 4 + 2] = *(bgcolor + 0); gif->canvas[(i + k) * 4 + 3] = opa; } i += gif->width; } #endif break; case 3: /* Restore to previous, i.e., don't update canvas.*/ break; default: /* Add frame non-transparent pixels to canvas. */ render_frame_rect(gif, gif->canvas); } } /* Return 1 if got a frame; 0 if got GIF trailer; -1 if error. */ int gd_get_frame(gd_GIF * gif) { char sep; dispose(gif); f_gif_read(gif, &sep, 1); while(sep != ',') { if(sep == ';') { f_gif_seek(gif, gif->anim_start, LV_FS_SEEK_SET); if(gif->loop_count == 1 || gif->loop_count < 0) { return 0; } else if(gif->loop_count > 1) { gif->loop_count--; } } else if(sep == '!') read_ext(gif); else return -1; f_gif_read(gif, &sep, 1); } if(read_image(gif) == -1) return -1; return 1; } void gd_render_frame(gd_GIF * gif, uint8_t * buffer) { render_frame_rect(gif, buffer); } void gd_rewind(gd_GIF * gif) { gif->loop_count = -1; f_gif_seek(gif, gif->anim_start, LV_FS_SEEK_SET); } void gd_close_gif(gd_GIF * gif) { f_gif_close(gif); lv_free(gif); } static bool f_gif_open(gd_GIF * gif, const void * path, bool is_file) { gif->f_rw_p = 0; gif->data = NULL; gif->is_file = is_file; if(is_file) { lv_fs_res_t res = lv_fs_open(&gif->fd, path, LV_FS_MODE_RD); if(res != LV_FS_RES_OK) return false; else return true; } else { gif->data = path; return true; } } static void f_gif_read(gd_GIF * gif, void * buf, size_t len) { if(gif->is_file) { lv_fs_read(&gif->fd, buf, len, NULL); } else { memcpy(buf, &gif->data[gif->f_rw_p], len); gif->f_rw_p += len; } } static int f_gif_seek(gd_GIF * gif, size_t pos, int k) { if(gif->is_file) { lv_fs_seek(&gif->fd, pos, k); uint32_t x; lv_fs_tell(&gif->fd, &x); return x; } else { if(k == LV_FS_SEEK_CUR) gif->f_rw_p += pos; else if(k == LV_FS_SEEK_SET) gif->f_rw_p = pos; return gif->f_rw_p; } } static void f_gif_close(gd_GIF * gif) { if(gif->is_file) { lv_fs_close(&gif->fd); } }