/******************************************************************************* * GIFDEC Wrapper Class * * Rewrite from: https://github.com/BasementCat/arduino-tft-gif ******************************************************************************/ #ifndef _GIFCLASS_H_ #define _GIFCLASS_H_ /* Wio Terminal */ #if defined(ARDUINO_ARCH_SAMD) && defined(SEEED_GROVE_UI_WIRELESS) #include #elif defined(ESP32) || defined(ESP8266) #include #else #include #endif #include #ifndef MIN #define MIN(A, B) ((A) < (B) ? (A) : (B)) #endif #ifndef MAX #define MAX(A, B) ((A) > (B) ? (A) : (B)) #endif #define GIF_BUF_SIZE 1024 typedef struct gd_Palette { int16_t len; uint16_t colors[256]; } gd_Palette; typedef struct gd_GCE { uint16_t delay; uint8_t tindex; uint8_t disposal; uint8_t input; uint8_t transparency; } gd_GCE; typedef struct gd_Entry { int32_t len; uint16_t prefix; uint8_t suffix; } gd_Entry; typedef struct gd_Table { int16_t bulk; int16_t nentries; gd_Entry *entries; } gd_Table; typedef struct gd_GIF { File *fd; off_t anim_start; uint16_t width, height; uint16_t depth; uint16_t loop_count; gd_GCE gce; gd_Palette *palette; gd_Palette lct, gct; void (*plain_text)( struct gd_GIF *gif, uint16_t tx, uint16_t ty, uint16_t tw, uint16_t th, uint8_t cw, uint8_t ch, uint8_t fg, uint8_t bg); void (*comment)(struct gd_GIF *gif); void (*application)(struct gd_GIF *gif, char id[8], char auth[3]); uint16_t fx, fy, fw, fh; uint8_t bgindex; gd_Table *table; bool processed_first_frame; } gd_GIF; class GifClass { public: gd_GIF *gd_open_gif(File *fd) { uint8_t sigver[3]; uint16_t width, height, depth; uint8_t fdsz, bgidx, aspect; int16_t gct_sz; gd_GIF *gif; // init global variables gif_buf_last_idx = GIF_BUF_SIZE; gif_buf_idx = gif_buf_last_idx; // no buffer yet file_pos = 0; /* Header */ gif_buf_read(fd, sigver, 3); if (memcmp(sigver, "GIF", 3) != 0) { Serial.println(F("invalid signature")); return NULL; } /* Version */ gif_buf_read(fd, sigver, 3); if (memcmp(sigver, "89a", 3) != 0) { Serial.println(F("invalid version")); return NULL; } /* Width x Height */ width = gif_buf_read16(fd); height = gif_buf_read16(fd); /* FDSZ */ gif_buf_read(fd, &fdsz, 1); /* Presence of GCT */ if (!(fdsz & 0x80)) { Serial.println(F("no global color table")); return NULL; } /* Color Space's Depth */ depth = ((fdsz >> 4) & 7) + 1; /* Ignore Sort Flag. */ /* GCT Size */ gct_sz = 1 << ((fdsz & 0x07) + 1); /* Background Color Index */ gif_buf_read(fd, &bgidx, 1); /* Aspect Ratio */ gif_buf_read(fd, &aspect, 1); /* Create gd_GIF Structure. */ gif = (gd_GIF *)calloc(1, sizeof(*gif)); gif->fd = fd; gif->width = width; gif->height = height; gif->depth = depth; /* Read GCT */ read_palette(fd, &gif->gct, gct_sz); gif->palette = &gif->gct; gif->bgindex = bgidx; gif->anim_start = file_pos; // fd->position(); gif->table = new_table(); gif->processed_first_frame = false; return gif; } /* Return 1 if got a frame; 0 if got GIF trailer; -1 if error. */ int32_t gd_get_frame(gd_GIF *gif, uint8_t *frame) { char sep; while (1) { gif_buf_read(gif->fd, (uint8_t *)&sep, 1); if (sep == 0) { gif_buf_read(gif->fd, (uint8_t *)&sep, 1); } if (sep == ',') { break; } if (sep == ';') { return 0; } if (sep == '!') { read_ext(gif); } else { Serial.print(F("Read sep: [")); Serial.print(sep); Serial.println(F("].\n")); return -1; } } // Serial.println("Do read image"); if (read_image(gif, frame) == -1) return -1; return 1; } void gd_rewind(gd_GIF *gif) { #if defined(ESP32) || defined(ESP8266) gif->fd->seek(gif->anim_start, SeekSet); #else gif->fd->seek(gif->anim_start); #endif file_pos = gif->anim_start; gif_buf_idx = gif_buf_last_idx; // reset buffer } void gd_close_gif(gd_GIF *gif) { gif->fd->close(); free(gif->table); free(gif); } private: bool gif_buf_seek(File *fd, int16_t len) { if (len > (gif_buf_last_idx - gif_buf_idx)) { #if defined(ESP32) || defined(ESP8266) // fd->seek(len - (gif_buf_last_idx - gif_buf_idx), SeekCur); fd->seek(file_pos + len - (gif_buf_last_idx - gif_buf_idx), SeekSet); #else fd->seek(file_pos + len - (gif_buf_last_idx - gif_buf_idx)); #endif gif_buf_idx = gif_buf_last_idx; } else { gif_buf_idx += len; } file_pos += len; return true; } int16_t gif_buf_read(File *fd, uint8_t *dest, int16_t len) { while (len--) { if (gif_buf_idx == gif_buf_last_idx) { gif_buf_last_idx = fd->read(gif_buf, GIF_BUF_SIZE); gif_buf_idx = 0; } file_pos++; *(dest++) = gif_buf[gif_buf_idx++]; } return len; } uint8_t gif_buf_read(File *fd) { if (gif_buf_idx == gif_buf_last_idx) { gif_buf_last_idx = fd->read(gif_buf, GIF_BUF_SIZE); gif_buf_idx = 0; } file_pos++; return gif_buf[gif_buf_idx++]; } uint16_t gif_buf_read16(File *fd) { return gif_buf_read(fd) + (((uint16_t)gif_buf_read(fd)) << 8); } void read_palette(File *fd, gd_Palette *dest, int16_t num_colors) { uint8_t r, g, b; dest->len = num_colors; for (int16_t i = 0; i < num_colors; i++) { r = gif_buf_read(fd); g = gif_buf_read(fd); b = gif_buf_read(fd); dest->colors[i] = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | ((b & 0xF8) >> 3); } } void discard_sub_blocks(gd_GIF *gif) { uint8_t len; do { gif_buf_read(gif->fd, &len, 1); gif_buf_seek(gif->fd, len); } while (len); } void read_plain_text_ext(gd_GIF *gif) { if (gif->plain_text) { uint16_t tx, ty, tw, th; uint8_t cw, ch, fg, bg; gif_buf_seek(gif->fd, 1); /* block size = 12 */ tx = gif_buf_read16(gif->fd); ty = gif_buf_read16(gif->fd); tw = gif_buf_read16(gif->fd); th = gif_buf_read16(gif->fd); cw = gif_buf_read(gif->fd); ch = gif_buf_read(gif->fd); fg = gif_buf_read(gif->fd); bg = gif_buf_read(gif->fd); gif->plain_text(gif, tx, ty, tw, th, cw, ch, fg, bg); } else { /* Discard plain text metadata. */ gif_buf_seek(gif->fd, 13); } /* Discard plain text sub-blocks. */ discard_sub_blocks(gif); } void read_graphic_control_ext(gd_GIF *gif) { uint8_t rdit; /* Discard block size (always 0x04). */ gif_buf_seek(gif->fd, 1); gif_buf_read(gif->fd, &rdit, 1); gif->gce.disposal = (rdit >> 2) & 3; gif->gce.input = rdit & 2; gif->gce.transparency = rdit & 1; gif->gce.delay = gif_buf_read16(gif->fd); gif_buf_read(gif->fd, &gif->gce.tindex, 1); /* Skip block terminator. */ gif_buf_seek(gif->fd, 1); } void read_comment_ext(gd_GIF *gif) { if (gif->comment) { gif->comment(gif); } /* Discard comment sub-blocks. */ discard_sub_blocks(gif); } void read_application_ext(gd_GIF *gif) { char app_id[8]; char app_auth_code[3]; /* Discard block size (always 0x0B). */ gif_buf_seek(gif->fd, 1); /* Application Identifier. */ gif_buf_read(gif->fd, (uint8_t *)app_id, 8); /* Application Authentication Code. */ gif_buf_read(gif->fd, (uint8_t *)app_auth_code, 3); if (!strncmp(app_id, "NETSCAPE", sizeof(app_id))) { /* Discard block size (0x03) and constant byte (0x01). */ gif_buf_seek(gif->fd, 2); gif->loop_count = gif_buf_read16(gif->fd); /* Skip block terminator. */ gif_buf_seek(gif->fd, 1); } else if (gif->application) { gif->application(gif, app_id, app_auth_code); discard_sub_blocks(gif); } else { discard_sub_blocks(gif); } } void read_ext(gd_GIF *gif) { uint8_t label; gif_buf_read(gif->fd, &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: Serial.print("unknown extension: "); Serial.println(label, HEX); } } gd_Table *new_table() { // uint16_t key; // int16_t init_bulk = MAX(1 << (key_size + 1), 0x100); // Table *table = (Table*) 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; int32_t s = sizeof(gd_Table) + (sizeof(gd_Entry) * 4096); gd_Table *table = (gd_Table *)malloc(s); if (table) { Serial.print(F("new_table() malloc: ")); Serial.println(s); } else { Serial.print(F("new_table() malloc failed: ")); Serial.println(s); } table->entries = (gd_Entry *)&table[1]; return table; } void reset_table(gd_Table *table, uint16_t key_size) { table->nentries = (1 << key_size) + 2; for (uint16_t key = 0; key < (1 << key_size); key++) { table->entries[key] = (gd_Entry){1, 0xFFF, (uint8_t)key}; } } /* Add table entry. Return value: * 0 on success * +1 if key size must be incremented after this addition * -1 if could not realloc table */ int32_t add_entry(gd_Table *table, int32_t len, uint16_t prefix, uint8_t suffix) { // Table *table = *tablep; // if (table->nentries == table->bulk) { // table->bulk *= 2; // table = (Table*) realloc(table, sizeof(*table) + sizeof(Entry) * table->bulk); // if (!table) return -1; // table->entries = (Entry *) &table[1]; // *tablep = table; // } table->entries[table->nentries] = (gd_Entry){len, prefix, suffix}; table->nentries++; if ((table->nentries & (table->nentries - 1)) == 0) return 1; return 0; } uint16_t get_key(gd_GIF *gif, uint16_t key_size, uint8_t *sub_len, uint8_t *shift, uint8_t *byte) { int16_t bits_read; int16_t rpad; int16_t 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) gif_buf_read(gif->fd, sub_len, 1); /* Must be nonzero! */ gif_buf_read(gif->fd, 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; } /* Compute output index of y-th input line, in frame of height h. */ int16_t interlaced_line_index(int16_t h, int16_t y) { int16_t 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). */ int8_t read_image_data(gd_GIF *gif, int16_t interlace, uint8_t *frame) { uint8_t sub_len, shift, byte, table_is_full = 0; uint16_t init_key_size, key_size; int32_t frm_off, str_len = 0, p, x, y; uint16_t key, clear, stop; int32_t ret; gd_Entry entry = {0, 0, 0}; // Serial.println("Read key size"); gif_buf_read(gif->fd, &byte, 1); key_size = (uint16_t)byte; // Serial.println("Set pos, discard sub blocks"); // start = gif->fd->position(); // discard_sub_blocks(gif); // end = gif->fd->position(); // gif_buf_seek(gif->fd, start, SeekSet); clear = 1 << key_size; stop = clear + 1; // Serial.println("New LZW table"); // table = new_table(key_size); reset_table(gif->table, key_size); key_size++; init_key_size = key_size; sub_len = shift = 0; // Serial.println("Get init key"); key = get_key(gif, key_size, &sub_len, &shift, &byte); /* clear code */ frm_off = 0; ret = 0; while (1) { if (key == clear) { // Serial.println("Clear key, reset nentries"); key_size = init_key_size; gif->table->nentries = (1 << (key_size - 1)) + 2; table_is_full = 0; } else if (!table_is_full) { // Serial.println("Add entry to table"); ret = add_entry(gif->table, str_len + 1, key, entry.suffix); // if (ret == -1) { // // Serial.println("Table entry add failure"); // free(table); // return -1; // } if (gif->table->nentries == 0x1000) { // Serial.println("Table is full"); ret = 0; table_is_full = 1; } } // Serial.println("Get key"); key = get_key(gif, key_size, &sub_len, &shift, &byte); if (key == clear) continue; if (key == stop) break; if (ret == 1) key_size++; entry = gif->table->entries[key]; str_len = entry.len; uint8_t tindex = gif->gce.tindex; // Serial.println("Interpret key"); while (1) { p = frm_off + entry.len - 1; x = p % gif->fw; y = p / gif->fw; if (interlace) { y = interlaced_line_index((int16_t)gif->fh, y); } if ((!gif->processed_first_frame) || (tindex != entry.suffix)) { frame[(gif->fy + y) * gif->width + gif->fx + x] = entry.suffix; } if (entry.prefix == 0xFFF) break; else entry = gif->table->entries[entry.prefix]; } frm_off += str_len; if (key < gif->table->nentries - 1 && !table_is_full) gif->table->entries[gif->table->nentries - 1].suffix = entry.suffix; } // Serial.println("Done w/ img data, free table and seek to end"); // free(table); gif_buf_read(gif->fd, &sub_len, 1); /* Must be zero! */ // gif_buf_seek(gif->fd, end, SeekSet); gif->processed_first_frame = true; return 0; } /* Read image. * Return 0 on success or -1 on out-of-memory (w.r.t. LZW code table). */ int8_t read_image(gd_GIF *gif, uint8_t *frame) { uint8_t fisrz; int16_t interlace; /* Image Descriptor. */ // Serial.println("Read image descriptor"); gif->fx = gif_buf_read16(gif->fd); gif->fy = gif_buf_read16(gif->fd); gif->fw = gif_buf_read16(gif->fd); gif->fh = gif_buf_read16(gif->fd); // Serial.println("Read fisrz?"); gif_buf_read(gif->fd, &fisrz, 1); interlace = fisrz & 0x40; /* Ignore Sort Flag. */ /* Local Color Table? */ if (fisrz & 0x80) { /* Read LCT */ // Serial.println("Read LCT"); read_palette(gif->fd, &gif->lct, 1 << ((fisrz & 0x07) + 1)); gif->palette = &gif->lct; } else { gif->palette = &gif->gct; } /* Image Data. */ // Serial.println("Read image data"); return read_image_data(gif, interlace, frame); } void render_frame_rect(gd_GIF *gif, uint16_t *buffer, uint8_t *frame) { int16_t i, j, k; uint8_t index; i = gif->fy * gif->width + gif->fx; for (j = 0; j < gif->fh; j++) { for (k = 0; k < gif->fw; k++) { index = frame[(gif->fy + j) * gif->width + gif->fx + k]; // color = &gif->palette->colors[index*2]; if ((!gif->gce.transparency) || (index != gif->gce.tindex)) { buffer[(i + k)] = gif->palette->colors[index]; } // memcpy(&buffer[(i+k)*2], color, 2); } i += gif->width; } } int16_t gif_buf_last_idx, gif_buf_idx, file_pos; uint8_t gif_buf[GIF_BUF_SIZE]; }; #endif /* _GIFCLASS_H_ */