Initial commit
This commit is contained in:
@@ -0,0 +1,650 @@
|
||||
/*******************************************************************************
|
||||
* 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 <Seeed_FS.h>
|
||||
#elif defined(ESP32) || defined(ESP8266)
|
||||
#include <FS.h>
|
||||
#else
|
||||
#include <SD.h>
|
||||
#endif
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#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
|
||||
{
|
||||
uint8_t size;
|
||||
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 length;
|
||||
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;
|
||||
} 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;
|
||||
int32_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();
|
||||
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, int32_t num_colors)
|
||||
{
|
||||
uint8_t r, g, b;
|
||||
dest->size = num_colors;
|
||||
for (int32_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 size;
|
||||
|
||||
do
|
||||
{
|
||||
gif_buf_read(gif->fd, &size, 1);
|
||||
gif_buf_seek(gif->fd, size);
|
||||
} while (size);
|
||||
}
|
||||
|
||||
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 length, 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){length, 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.length;
|
||||
uint8_t tindex = gif->gce.tindex;
|
||||
// Serial.println("Interpret key");
|
||||
while (1)
|
||||
{
|
||||
p = frm_off + entry.length - 1;
|
||||
x = p % gif->fw;
|
||||
y = p / gif->fw;
|
||||
if (interlace)
|
||||
{
|
||||
y = interlaced_line_index((int16_t)gif->fh, y);
|
||||
}
|
||||
if (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);
|
||||
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_ */
|
||||
@@ -0,0 +1,301 @@
|
||||
/*******************************************************************************
|
||||
* Animated GIF Image Viewer
|
||||
* This is a simple Animated GIF image viewer exsample
|
||||
* Image Source: https://www.geocities.ws/finalfantasyfive/ff5animations.html
|
||||
* optimized with ezgif.com
|
||||
*
|
||||
* Setup steps:
|
||||
* 1. Change your LCD parameters in Arduino_GFX setting
|
||||
* 2. Upload Animated GIF file
|
||||
* FFat (ESP32):
|
||||
* upload FFat (FatFS) data with ESP32 Sketch Data Upload:
|
||||
* ESP32: https://github.com/lorol/arduino-esp32fs-plugin
|
||||
* LittleFS (ESP32 / ESP8266 / Pico):
|
||||
* upload LittleFS data with ESP8266 LittleFS Data Upload:
|
||||
* ESP32: https://github.com/lorol/arduino-esp32fs-plugin
|
||||
* ESP8266: https://github.com/earlephilhower/arduino-esp8266littlefs-plugin
|
||||
* Pico: https://github.com/earlephilhower/arduino-pico-littlefs-plugin.git
|
||||
* SPIFFS (ESP32):
|
||||
* upload SPIFFS data with ESP32 Sketch Data Upload:
|
||||
* ESP32: https://github.com/lorol/arduino-esp32fs-plugin
|
||||
******************************************************************************/
|
||||
#define GIF_FILENAME1 "/jobs.gif"
|
||||
#define GIF_FILENAME2 "/archer.gif"
|
||||
#define GIF_FILENAME3 "/white.gif"
|
||||
#define GIF_FILENAME4 "/lancer.gif"
|
||||
|
||||
/*******************************************************************************
|
||||
* Start of Arduino_GFX setting
|
||||
******************************************************************************/
|
||||
#include <Arduino_GFX_Library.h>
|
||||
|
||||
/* all display share same SPI Data Bus with individual CS, RST pins connected to MCU RST pin */
|
||||
Arduino_DataBus *bus1 = new Arduino_HWSPI(DF_GFX_DC, 21 /* CS */);
|
||||
Arduino_GFX *gfx1 = new Arduino_SSD1331(bus1, GFX_NOT_DEFINED /* RST */, 0 /* rotation */);
|
||||
|
||||
Arduino_DataBus *bus2 = new Arduino_HWSPI(DF_GFX_DC, 32 /* CS */);
|
||||
Arduino_GFX *gfx2 = new Arduino_ST7735(bus2, GFX_NOT_DEFINED /* RST */, 3 /* rotation */, true /* IPS */, 80 /* width */, 160 /* height */, 26 /* col offset 1 */, 1 /* row offset 1 */, 26 /* col offset 2 */, 1 /* row offset 2 */);
|
||||
|
||||
Arduino_DataBus *bus3 = new Arduino_HWSPI(DF_GFX_DC, 22 /* CS */);
|
||||
Arduino_GFX *gfx3 = new Arduino_ST7789(bus3, GFX_NOT_DEFINED /* RST */, 0 /* rotation */, true /* IPS */, 240 /* width */, 240 /* height */, 0 /* col offset 1 */, 0 /* row offset 1 */, 0 /* col offset 2 */, 80 /* row offset 2 */);
|
||||
|
||||
Arduino_DataBus *bus4 = new Arduino_HWSPI(DF_GFX_DC, 5 /* CS */);
|
||||
Arduino_GFX *gfx4 = new Arduino_ILI9341(bus4, GFX_NOT_DEFINED /* RST */, 0 /* rotation */, false /* IPS */);
|
||||
/*******************************************************************************
|
||||
* End of Arduino_GFX setting
|
||||
******************************************************************************/
|
||||
|
||||
#if defined(ARDUINO_RASPBERRY_PI_PICO) || defined(ARDUINO_RASPBERRY_PI_PICO_W)
|
||||
#include <LittleFS.h>
|
||||
#include <SD.h>
|
||||
#elif defined(ESP32)
|
||||
#include <FFat.h>
|
||||
#include <LittleFS.h>
|
||||
#include <SPIFFS.h>
|
||||
#include <SD.h>
|
||||
#elif defined(ESP8266)
|
||||
#include <LittleFS.h>
|
||||
#include <SD.h>
|
||||
#else
|
||||
#include <SD.h>
|
||||
#endif
|
||||
|
||||
#include "GifClass.h"
|
||||
static GifClass gifClass1;
|
||||
static GifClass gifClass2;
|
||||
static GifClass gifClass3;
|
||||
static GifClass gifClass4;
|
||||
|
||||
void setup()
|
||||
{
|
||||
Serial.begin(115200);
|
||||
// while (!Serial);
|
||||
Serial.println("Arduino_GFX library Multiple Device Test!");
|
||||
|
||||
gfx1->begin();
|
||||
gfx1->fillScreen(RED);
|
||||
delay(200);
|
||||
|
||||
gfx2->begin();
|
||||
gfx2->fillScreen(YELLOW);
|
||||
delay(200);
|
||||
|
||||
gfx3->begin();
|
||||
gfx3->fillScreen(GREEN);
|
||||
delay(200);
|
||||
|
||||
gfx4->begin();
|
||||
gfx4->fillScreen(BLUE);
|
||||
delay(200);
|
||||
|
||||
#if defined(ARDUINO_RASPBERRY_PI_PICO) || defined(ARDUINO_RASPBERRY_PI_PICO_W)
|
||||
if (!LittleFS.begin())
|
||||
// if (!SD.begin(SS))
|
||||
#elif defined(ESP32)
|
||||
// if (!FFat.begin())
|
||||
if (!LittleFS.begin())
|
||||
// if (!SPIFFS.begin())
|
||||
// if (!SD.begin(SS))
|
||||
#elif defined(ESP8266)
|
||||
if (!LittleFS.begin())
|
||||
// if (!SD.begin(SS))
|
||||
#else
|
||||
if (!SD.begin())
|
||||
#endif
|
||||
{
|
||||
Serial.println(F("ERROR: File System Mount Failed!"));
|
||||
gfx4->println(F("ERROR: File System Mount Failed!"));
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
void loop()
|
||||
{
|
||||
#if defined(ARDUINO_RASPBERRY_PI_PICO) || defined(ARDUINO_RASPBERRY_PI_PICO_W)
|
||||
File gifFile1 = LittleFS.open(GIF_FILENAME1, "r");
|
||||
File gifFile2 = LittleFS.open(GIF_FILENAME2, "r");
|
||||
File gifFile3 = LittleFS.open(GIF_FILENAME3, "r");
|
||||
File gifFile4 = LittleFS.open(GIF_FILENAME4, "r");
|
||||
#elif defined(ESP32)
|
||||
File gifFile1 = LittleFS.open(GIF_FILENAME1, "r");
|
||||
File gifFile2 = LittleFS.open(GIF_FILENAME2, "r");
|
||||
File gifFile3 = LittleFS.open(GIF_FILENAME3, "r");
|
||||
File gifFile4 = LittleFS.open(GIF_FILENAME4, "r");
|
||||
#elif defined(ESP8266)
|
||||
File gifFile1 = LittleFS.open(GIF_FILENAME1, "r");
|
||||
File gifFile2 = LittleFS.open(GIF_FILENAME2, "r");
|
||||
File gifFile3 = LittleFS.open(GIF_FILENAME3, "r");
|
||||
File gifFile4 = LittleFS.open(GIF_FILENAME4, "r");
|
||||
#else
|
||||
File gifFile1 = SD.open(GIF_FILENAME1, FILE_READ);
|
||||
File gifFile2 = SD.open(GIF_FILENAME2, FILE_READ);
|
||||
File gifFile3 = SD.open(GIF_FILENAME3, FILE_READ);
|
||||
File gifFile4 = SD.open(GIF_FILENAME4, FILE_READ);
|
||||
#endif
|
||||
|
||||
if (!gifFile1 || gifFile1.isDirectory())
|
||||
{
|
||||
Serial.println(F("ERROR: open gifFile1 Failed!"));
|
||||
gfx1->println(F("ERROR: open gifFile1 Failed!"));
|
||||
}
|
||||
else if (!gifFile2 || gifFile2.isDirectory())
|
||||
{
|
||||
Serial.println(F("ERROR: open gifFile2 Failed!"));
|
||||
gfx2->println(F("ERROR: open gifFile2 Failed!"));
|
||||
}
|
||||
else if (!gifFile3 || gifFile3.isDirectory())
|
||||
{
|
||||
Serial.println(F("ERROR: open gifFile3 Failed!"));
|
||||
gfx3->println(F("ERROR: open gifFile3 Failed!"));
|
||||
}
|
||||
else if (!gifFile4 || gifFile4.isDirectory())
|
||||
{
|
||||
Serial.println(F("ERROR: open gifFile4 Failed!"));
|
||||
gfx4->println(F("ERROR: open gifFile4 Failed!"));
|
||||
}
|
||||
else
|
||||
{
|
||||
// read GIF file header
|
||||
gd_GIF *gif1 = gifClass1.gd_open_gif(&gifFile1);
|
||||
gd_GIF *gif2 = gifClass2.gd_open_gif(&gifFile2);
|
||||
gd_GIF *gif3 = gifClass3.gd_open_gif(&gifFile3);
|
||||
gd_GIF *gif4 = gifClass4.gd_open_gif(&gifFile4);
|
||||
if (!gif1)
|
||||
{
|
||||
Serial.println(F("gd_open_gif(&gifFile1) failed!"));
|
||||
}
|
||||
else if (!gif2)
|
||||
{
|
||||
Serial.println(F("gd_open_gif(&gifFile2) failed!"));
|
||||
}
|
||||
else if (!gif3)
|
||||
{
|
||||
Serial.println(F("gd_open_gif(&gifFile3) failed!"));
|
||||
}
|
||||
else if (!gif4)
|
||||
{
|
||||
Serial.println(F("gd_open_gif(&gifFile4) failed!"));
|
||||
}
|
||||
else
|
||||
{
|
||||
uint8_t *buf1 = (uint8_t *)malloc(gif1->width * gif1->height);
|
||||
uint8_t *buf2 = (uint8_t *)malloc(gif2->width * gif2->height);
|
||||
uint8_t *buf3 = (uint8_t *)malloc(gif3->width * gif3->height);
|
||||
uint8_t *buf4 = (uint8_t *)malloc(gif4->width * gif4->height);
|
||||
if (!buf1)
|
||||
{
|
||||
Serial.println(F("buf1 malloc failed!"));
|
||||
}
|
||||
if (!buf2)
|
||||
{
|
||||
Serial.println(F("buf2 malloc failed!"));
|
||||
}
|
||||
if (!buf3)
|
||||
{
|
||||
Serial.println(F("buf3 malloc failed!"));
|
||||
}
|
||||
if (!buf4)
|
||||
{
|
||||
Serial.println(F("buf4 malloc failed!"));
|
||||
}
|
||||
else
|
||||
{
|
||||
int16_t x1 = (gfx1->width() - gif1->width) / 2;
|
||||
int16_t y1 = (gfx1->height() - gif1->height) / 2;
|
||||
int16_t x2 = (gfx2->width() - gif2->width) / 2;
|
||||
int16_t y2 = (gfx2->height() - gif2->height) / 2;
|
||||
int16_t x3 = (gfx3->width() - gif3->width) / 2;
|
||||
int16_t y3 = (gfx3->height() - gif3->height) / 2;
|
||||
int16_t x4 = (gfx4->width() - gif4->width) / 2;
|
||||
int16_t y4 = (gfx4->height() - gif4->height) / 2;
|
||||
|
||||
Serial.println(F("GIF video start"));
|
||||
int32_t res1, res2, res3, res4;
|
||||
uint32_t duration = 0, remain = 0;
|
||||
while (1)
|
||||
{
|
||||
res1 = gifClass1.gd_get_frame(gif1, buf1);
|
||||
res2 = gifClass2.gd_get_frame(gif2, buf2);
|
||||
res3 = gifClass3.gd_get_frame(gif3, buf3);
|
||||
res4 = gifClass4.gd_get_frame(gif4, buf4);
|
||||
if (res1 < 0)
|
||||
{
|
||||
Serial.println(F("ERROR: gd_get_frame(gif1, buf1) failed!"));
|
||||
break;
|
||||
}
|
||||
else if (res1 == 0)
|
||||
{
|
||||
Serial.println(F("rewind gif1"));
|
||||
gifClass1.gd_rewind(gif1);
|
||||
}
|
||||
else
|
||||
{
|
||||
gfx1->drawIndexedBitmap(x1, y1, buf1, gif1->palette->colors, gif1->width, gif1->height);
|
||||
}
|
||||
|
||||
if (res2 < 0)
|
||||
{
|
||||
Serial.println(F("ERROR: gd_get_frame(gif2, buf2) failed!"));
|
||||
break;
|
||||
}
|
||||
else if (res2 == 0)
|
||||
{
|
||||
Serial.println(F("rewind gif2"));
|
||||
gifClass2.gd_rewind(gif2);
|
||||
}
|
||||
else
|
||||
{
|
||||
gfx2->drawIndexedBitmap(x2, y2, buf2, gif2->palette->colors, gif2->width, gif2->height);
|
||||
}
|
||||
|
||||
if (res3 < 0)
|
||||
{
|
||||
Serial.println(F("ERROR: gd_get_frame(gif3, buf3) failed!"));
|
||||
break;
|
||||
}
|
||||
else if (res3 == 0)
|
||||
{
|
||||
Serial.println(F("rewind gif3"));
|
||||
gifClass3.gd_rewind(gif3);
|
||||
}
|
||||
else
|
||||
{
|
||||
gfx3->drawIndexedBitmap(x3, y3, buf3, gif3->palette->colors, gif3->width, gif3->height);
|
||||
}
|
||||
|
||||
if (res4 < 0)
|
||||
{
|
||||
Serial.println(F("ERROR: gd_get_frame(gif4, buf4) failed!"));
|
||||
break;
|
||||
}
|
||||
else if (res4 == 0)
|
||||
{
|
||||
Serial.println(F("rewind gif4"));
|
||||
gifClass4.gd_rewind(gif4);
|
||||
}
|
||||
else
|
||||
{
|
||||
gfx4->drawIndexedBitmap(x4, y4, buf4, gif4->palette->colors, gif4->width, gif4->height);
|
||||
}
|
||||
}
|
||||
Serial.println(F("GIF video end"));
|
||||
Serial.print(F("duration: "));
|
||||
Serial.print(duration);
|
||||
Serial.print(F(", remain: "));
|
||||
Serial.print(remain);
|
||||
Serial.print(F(" ("));
|
||||
Serial.print(100.0 * remain / duration);
|
||||
Serial.println(F("%)"));
|
||||
|
||||
gifClass1.gd_close_gif(gif1);
|
||||
gifClass2.gd_close_gif(gif2);
|
||||
gifClass3.gd_close_gif(gif3);
|
||||
gifClass4.gd_close_gif(gif4);
|
||||
free(buf1);
|
||||
free(buf2);
|
||||
free(buf3);
|
||||
free(buf4);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.6 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 27 KiB |
Reference in New Issue
Block a user