tommyho陳亮
Published © Apache-2.0

Arduino Animated GIF Player 2022

Play any animated GIF file from SPIFFS directly onto TFT screen as-is, WITHOUT converting to RGB565 or PROGMEM as script.

BeginnerFull instructions provided1 hour14,693

Things used in this project

Hardware components

M5StickC ESP32-PICO Mini IoT Development Board
M5Stack M5StickC ESP32-PICO Mini IoT Development Board
Tested on this device
×1
TTGO T-Display
Tested on this device
×1

Software apps and online services

Arduino IDE
Arduino IDE
Arduino_Gfx
ezgif

Story

Read more

Schematics

PINOUT: TTGO T-Display

PINOUT: TTGO T-Display

PINOUT: M5Stack M5Stick-C

Code

espgfxGIF.ino

Arduino
Please note the complete file is on github. You must have all files and folder to work.
#include <SPIFFS.h>
#include <Arduino_GFX_Library.h>  /* Install via Arduino Library Manager */
#include "gifdec.h"

/*
 *  espgfxGIF Version 2022.05B (Brightness Edition)
 *  Board: TTGO T-Display & M5Stack M5StickC (esp32)
 *  Author: tommyho510@gmail.com
 *  Original Author: moononournation
 *  Required: Arduino library Arduino_GFX 1.2.1
 *  Dependency: gifdec.h
 *
 *  Please upload SPIFFS data with ESP32 Sketch Data Upload:
 *  https://github.com/me-no-dev/arduino-esp32fs-plugin
 *  GIF src: various
 */

// *** BEGIN editing of your settings ...
#define ARDUINO_TDISPLAY
//#define GIF_FILENAME "/your_file.gif" /* comment out for random GIF */
// *** END editing of your settings ...

//#define DEBUG /* uncomment this line to start with screen test */

#if defined(ARDUINO_M5STICKC)
/* M5Stack */
/* 0.96" ST7735 IPS LCD 80x160 M5Stick-C * (Rotation: 0 bottom up, 1 right, 2 top, 3 left) */
#include <M5StickC.h>
//#define TFT_MOSI 15
//#define TFT_SCLK 13
//#define TFT_CS   5
//#define TFT_DC   23
#define TFT_RST  18
#define TFT_BL 2
Arduino_DataBus *bus = new Arduino_ESP32SPI(23 /* DC */, 5 /* CS */, 13 /* SCK */, 15 /* MOSI */, -1 /* MISO */);
Arduino_ST7735 *gfx = new Arduino_ST7735(bus, TFT_RST /* RST */, 1 /* rotation */, true /* IPS */, 80 /* width */, 160 /* height */, 26 /* col offset 1 */, 1 /* row offset 1 */, 26 /* col offset 2 */, 1 /* row offset 2 */);
const int BTN_A = 37;
const int BTN_B = 39;

#elif defined(ARDUINO_TDISPLAY) 
/* TTGO T-Display */
/* 1.14" ST7789 IPS LCD 135x240 TTGO T-Display (Rotation: 0 top up, 1 left, 2 bottom, 3 right) */
//#define TFT_MOSI 19
//#define TFT_SCLK 18
//#define TFT_CS   5
//#define TFT_DC   16
#define TFT_RST -1
#define TFT_BL 4
Arduino_DataBus *bus = new Arduino_ESP32SPI(16 /* DC */, 5 /* CS */, 18 /* SCK */, 19 /* MOSI */, -1 /* MISO */);
Arduino_ST7789 *gfx = new Arduino_ST7789(bus, TFT_RST /* RST */, 1 /* rotation */, true /* IPS */, 135 /* width */, 240 /* height */, 52 /* col offset 1 */, 40 /* row offset 1 */, 53 /* col offset 2 */, 40 /* row offset 2 */);
const int BTN_A = 0;
const int BTN_B = 35;

#elif defined(ARDUINO_D1MINI) 
/* LOLIN D1 Mini esp8266 + TFT-2.4 Shield */
/* 2.8" ILI9341 TFT LCD 240x320 (Rotation: 0 top up, 1 left, 2 bottom, 3 right) */
#define TFT_MOSI 13
#define TFT_MISO 12
#define TFT_SCLK 14
#define TFT_CS   16
#define TFT_DC   15
#define TFT_RST  5
//#define TFT_BL NC
Arduino_DataBus *bus = new Arduino_ESP32SPI(TFT_DC /* DC */, TFT_CS /* CS */, TFT_SCLK /* SCK */, TFT_MOSI /* MOSI */, TFT_MISO /* MISO */);
Arduino_ILI9341 *gfx = new Arduino_ILI9341(bus, TFT_RST /* RST */, 0 /* rotation */, false /* IPS */);
const int BTN_A = 3;
const int BTN_B = 4;

#elif defined(ARDUINO_DEVKITV1) 
/* DOIT ESP32 DEVKIT V1 + ILI9341 */
/* 2.8" ILI9341 TFT LCD 240x320 (Rotation: 0 top up, 1 left, 2 bottom, 3 right) */
#define TFT_MOSI 23
#define TFT_MISO 19
#define TFT_SCLK 18
#define TFT_CS   5
#define TFT_DC   16
#define TFT_RST  17
//#define TFT_BL 4
Arduino_DataBus *bus = new Arduino_ESP32SPI(TFT_DC /* DC */, TFT_CS /* CS */, TFT_SCLK /* SCK */, TFT_MOSI /* MOSI */, TFT_MISO /* MISO */);
Arduino_ILI9341 *gfx = new Arduino_ILI9341(bus, TFT_RST /* RST */, 0 /* rotation */, false /* IPS */);
const int BTN_A = 13;
const int BTN_B = 15;

#endif /* not selected specific hardware */

// Rotation & Brightness control
int rot[2] = {1, 3};
int backlight[5] = {10, 30, 60, 120, 240};
const int pwmFreq = 5000;
const int pwmResolution = 8;
const int pwmLedChannelTFT = 0;
byte a = 1;
byte b = 4;
unsigned long p = 0;
bool inv = 0;
int pressA = 0;
int pressB = 0;

String gifArray[30], randGIF_FILENAME, playFile;
int gifArraySize;
  
void listSPIFFS() {
  gifArraySize = 0;
  Serial.println("{ls /:[");
  if (SPIFFS.begin(true)) {
    File root = SPIFFS.open("/");
    File file = root.openNextFile();
    while(file){
      gifArray[gifArraySize] = String(file.name());
      Serial.println(("  {#:" + String(gifArraySize) + ", name:" + String(file.name()) + ",                     ").substring(0,40) + "\tsize:" + String(file.size()) + "}");
      file = root.openNextFile();
      gifArraySize++;
    }
    randGIF_FILENAME = gifArray[random(0, gifArraySize)];
    //p = millis();
    //randGIF_FILENAME = gifArray[millis() % gifArraySize];
    Serial.println("  {randomGIF:" + String(randGIF_FILENAME) +"}");
    Serial.println("]}");
  }
}

void eraseSPIFFS() {
  if(SPIFFS.begin(true)) {
    bool formatted = SPIFFS.format();
    if(formatted) {
      Serial.println("\n\nSuccess formatting");
      listSPIFFS();
    } else {
      Serial.println("\n\nError formatting");
    }
  }
}

void adjGIF() {
  if (digitalRead(BTN_A) == 0) {
    if (pressA == 0) {
      pressA = 1;
      inv = !inv;
      gfx->invertDisplay(inv);
      Serial.println("{BTN_A:Pressed, Inverted:" + String(inv) + "}");
      //a++;
      //if (a >= 2)
      //  a = 0;
      //gfx->setRotation(rot[a]);
      //Serial.println("{BTN_A:Pressed, Rotation:" + String(rot[a]) + "}");
      //Serial.println("{BTN_A:Pressed}");
      listSPIFFS();
    }
  } else pressA = 0;
}

void adjBrightness() {
  if (digitalRead(BTN_B) == 0) {
    if (pressB == 0) {
      pressB = 1;
      b++;
      //if (b > 15) b = 7;
      //M5.Axp.ScreenBreath(b);
      if (b >= 5) b = 0;
      ledcWrite(pwmLedChannelTFT, backlight[b]);
      Serial.println("{BTN_B:Pressed, Brightness:" + String(backlight[b]) + "}");
    }
  } else pressB = 0;
}
    
void gfxPlayGIF() {
  // Init SPIFFS
  if (!SPIFFS.begin(true)) {
    Serial.println(F("ERROR: SPIFFS mount failed!"));
    gfx->println(F("ERROR: SPIFFS mount failed!"));
  } else {

#ifndef GIF_FILENAME
#define GIF_FILENAME
    playFile = "/" + String(randGIF_FILENAME);
	  Serial.println("{Opening random GIF_FILENAME " + playFile + "}");
#else
    playFile = GIF_FILENAME;
	  Serial.println("{Opening designated GIF_FILENAME " + playFile + "}");
#endif
    
    File vFile = SPIFFS.open(playFile);
    if (!vFile || vFile.isDirectory()) {
      Serial.println(F("ERROR: Failed to open file for reading"));
      gfx->println(F("ERROR: Failed to open file for reading"));
      gfx->println(playFile);
    } else {
      gd_GIF *gif = gd_open_gif(&vFile);
      if (!gif) {
        Serial.println(F("gd_open_gif() failed!"));
      } else {
        int32_t s = gif->width * gif->height;
        uint8_t *buf = (uint8_t *)malloc(s);
        if (!buf) {
          Serial.println(F("buf malloc failed!"));
        } else {
          Serial.println(F("{acion:play, GIF:started, Info:["));
          Serial.printf("  {canvas size: %ux%u}\n", gif->width, gif->height);
          Serial.printf("  {number of colors: %d}\n", gif->palette->size);
          Serial.println(F("]}"));
          
          int t_fstart, t_delay = 0, t_real_delay, res, delay_until;
          int duration = 0, remain = 0;
          while (1) {
            gfx->setAddrWindow((gfx->width() - gif->width) / 2, (gfx->height() - gif->height) / 2, gif->width, gif->height);
            t_fstart = millis();
            t_delay = gif->gce.delay * 10;
            res = gd_get_frame(gif, buf);
            if (res < 0) {
              Serial.println(F("ERROR: gd_get_frame() failed!"));
              break;
            } else if (res == 0) {
              Serial.printf("{action:rewind, duration:%d, remain:%d (%0.1f%%)}\n", duration, remain, 100.0 * remain / duration);
              duration = 0;
              remain = 0;
              gd_rewind(gif);
              continue;
            }
 
            gfx->startWrite();
            gfx->writeIndexedPixels(buf, gif->palette->colors, s);
            gfx->endWrite();

            t_real_delay = t_delay - (millis() - t_fstart);
            duration += t_delay;
            remain += t_real_delay;
            delay_until = millis() + t_real_delay;
            do {
              delay(1);
            } while (millis() < delay_until);

            adjBrightness();
            adjGIF();
          }
          Serial.println(F("action:stop, GIF:ended"));
          Serial.printf("{duration: %d, remain: %d (%0.1f %%)}\n", duration, remain, 100.0 * remain / duration);
          gd_close_gif(gif);
        }
      }
    }
  }
}

unsigned long testRainbow(uint8_t cIndex) {
  gfx->fillScreen(BLACK);
  unsigned long start = micros();
  int w = gfx->width(), h = gfx->height(), s = h / 8;
  uint16_t arr [] = { PINK, RED, ORANGE, YELLOW, GREEN, MAGENTA, BLUE, WHITE, PINK, RED, ORANGE, YELLOW, GREEN, MAGENTA, BLUE, WHITE };
  gfx->fillRect(0, 0, w, s, arr [cIndex]);
  gfx->fillRect(0, s, w, 2 * s, arr [cIndex + 1]);
  gfx->fillRect(0, 2 * s, w, 3 * s, arr [cIndex + 2]);
  gfx->fillRect(0, 3 * s, w, 4 * s, arr [cIndex + 3]);
  gfx->fillRect(0, 4 * s, w, 5 * s, arr [cIndex + 4]);
  gfx->fillRect(0, 5 * s, w, 6 * s, arr [cIndex + 5]);
  gfx->fillRect(0, 6 * s, w, 7 * s, arr [cIndex + 6]);
  gfx->fillRect(0, 7 * s, w, 8 * s, arr [cIndex + 7]);
  return micros() - start;
}

unsigned long testChar(uint16_t colorT, uint16_t colorB) {
  gfx->fillScreen(colorB);
  unsigned long start = micros();
  gfx->setTextColor(GREEN);
  for (int x = 0; x < 16; x++){
    gfx->setCursor(10 + x * 8, 2);
    gfx->print(x, 16);
  }
  gfx->setTextColor(BLUE);
  for (int y = 0; y < 16; y++){
    gfx->setCursor(2, 12 + y * 10);
    gfx->print(y, 16);
  }

  char c = 0;
  for (int y = 0; y < 16; y++){
    for (int x = 0; x < 16; x++){
      gfx->drawChar(10 + x * 8, 12 + y * 10, c++, colorT, colorB);
    }
  }
  return micros() - start;
}

unsigned long testFilledCircles(uint8_t radius, uint16_t color) {
  gfx->fillScreen(BLACK);
  unsigned long start;
  int x, y, r2 = radius * 2,
    w = gfx->width(), h = gfx->height();
  start = micros();
  for(x=radius; x<w; x+=r2) {
    for(y=radius; y<h; y+=r2) {
      gfx->fillCircle(x, y, radius, color);
    }
  }
  return micros() - start;
}

unsigned long testCircles(uint8_t radius, uint16_t color) {
  // gfx->fillScreen(BLACK);
  // Screen is not cleared for this one -- this is
  // intentional and does not affect the reported time.
  unsigned long start;
  int x, y, r2 = radius * 2,
    w = gfx->width()  + radius, h = gfx->height() + radius;
  start = micros();
  for(x=0; x<w; x+=r2) {
    for(y=0; y<h; y+=r2) {
      gfx->drawCircle(x, y, radius, color);
    }
  }
  return micros() - start;
}

void screen_test() {
	Serial.print(F("Draw Ranbow: "));
  Serial.println(testRainbow(0));
  delay(500);
  Serial.print(F("Draw Ranbow: "));
  Serial.println(testRainbow(2));
  delay(500);
  Serial.print(F("Draw Ranbow: "));
  Serial.println(testRainbow(4));
  delay(500);
  Serial.print(F("Draw Ranbow: "));
  Serial.println(testRainbow(6));
  delay(500);
  Serial.print(F("Draw Filled Circles: "));
  Serial.println(testFilledCircles(10, MAGENTA));
  delay(500);
  Serial.print(F("Draw Circles: "));
  Serial.println(testCircles(10, BLACK));
  delay(500);
  Serial.print(F("Draw Filled Circles: "));
  Serial.println(testFilledCircles(10, YELLOW));
  delay(500);
  Serial.print(F("Draw Circles: "));
  Serial.println(testCircles(10, BLUE));
  delay(500);
  Serial.print(F("Draw Filled Circles: "));
  Serial.println(testFilledCircles(10, RED));
  delay(500);
  Serial.print(F("Draw Circles: "));
  Serial.println(testCircles(10, WHITE));
  delay(500);
  Serial.print(F("Draw Text: "));
  Serial.println(testChar(WHITE, BLACK));
  delay(500);
  Serial.print(F("Draw Text: "));
  Serial.println(testChar(BLUE, WHITE));
  delay(500);
  gfx->fillScreen(BLACK);
}

void setup() {
  pinMode(BTN_A, INPUT_PULLUP);
  pinMode(BTN_B, INPUT);
#if defined(ARDUINO_M5STICKC)
  M5.begin();
#endif
  Serial.begin(115200);
  delay(500);
  Serial.println("{Device:Started}");

  listSPIFFS();
  //eraseSPIFFS();
     
  // Init Video
  gfx->begin();
  gfx->fillScreen(BLACK);

  // Turn on Backlight
#ifdef TFT_BL
  //M5.Axp.ScreenBreath(b);
  ledcSetup(pwmLedChannelTFT, pwmFreq, pwmResolution); // 5 kHz PWM, 8-bit resolution
  ledcAttachPin(TFT_BL, pwmLedChannelTFT);             // assign TFT_BL pin to channel
  ledcWrite(pwmLedChannelTFT, backlight[b]);           // brightness 0 - 255
#endif

#ifdef DEBUG
  screen_test();
#endif
 
  gfxPlayGIF();

  // Turn off Backlight
#ifdef TFT_BL
  delay(60000);
  ledcDetachPin(TFT_BL);
#endif

  // Put device to sleep
  gfx->displayOff();
  esp_deep_sleep_start();
}

void loop() {
}

espgfxGIF_Timer.ino

Arduino
This is the second iteration that auto reboots and plays randomly another GIF.
#include <SPIFFS.h>
#include <Arduino_GFX_Library.h>  /* Install via Arduino Library Manager */
#include "gifdec.h"

/*
 *  espgfxGIF Version 2023.02A(Brightness Edition)
 *  Board: TTGO T-Display & M5Stack M5StickC (esp32)
 *  Author: tommyho510@gmail.com
 *  Original Author: moononournation
 *  Required: Arduino library Arduino_GFX 1.2.1
 *  Dependency: gifdec.h
 *
 *  Please upload SPIFFS data with ESP32 Sketch Data Upload:
 *  https://github.com/me-no-dev/arduino-esp32fs-plugin
 *  GIF src: various
 */

// *** BEGIN editing of your settings ...
#define ARDUINO_TDISPLAY
//#define GIF_FILENAME "/your_file.gif" /* comment out for random GIF */
// *** END editing of your settings ...

//#define DEBUG /* uncomment this line to start with screen test */

#if defined(ARDUINO_M5STICKC)
/* M5Stack */
/* 0.96" ST7735 IPS LCD 80x160 M5Stick-C * (Rotation: 0 bottom up, 1 right, 2 top, 3 left) */
#include <M5StickC.h>
//#define TFT_MOSI 15
//#define TFT_SCLK 13
//#define TFT_CS   5
//#define TFT_DC   23
#define TFT_RST  18
#define TFT_BL 2
Arduino_DataBus *bus = new Arduino_ESP32SPI(23 /* DC */, 5 /* CS */, 13 /* SCK */, 15 /* MOSI */, -1 /* MISO */);
Arduino_ST7735 *gfx = new Arduino_ST7735(bus, TFT_RST /* RST */, 1 /* rotation */, true /* IPS */, 80 /* width */, 160 /* height */, 26 /* col offset 1 */, 1 /* row offset 1 */, 26 /* col offset 2 */, 1 /* row offset 2 */);
const int BTN_A = 37;
const int BTN_B = 39;

#elif defined(ARDUINO_TDISPLAY) 
/* TTGO T-Display */
/* 1.14" ST7789 IPS LCD 135x240 TTGO T-Display (Rotation: 0 top up, 1 left, 2 bottom, 3 right) */
//#define TFT_MOSI 19
//#define TFT_SCLK 18
//#define TFT_CS   5
//#define TFT_DC   16
#define TFT_RST -1
#define TFT_BL 4
Arduino_DataBus *bus = new Arduino_ESP32SPI(16 /* DC */, 5 /* CS */, 18 /* SCK */, 19 /* MOSI */, -1 /* MISO */);
Arduino_ST7789 *gfx = new Arduino_ST7789(bus, TFT_RST /* RST */, 1 /* rotation */, true /* IPS */, 135 /* width */, 240 /* height */, 52 /* col offset 1 */, 40 /* row offset 1 */, 53 /* col offset 2 */, 40 /* row offset 2 */);
const int BTN_A = 0;
const int BTN_B = 35;

#elif defined(ARDUINO_D1MINI) 
/* LOLIN D1 Mini esp8266 + TFT-2.4 Shield */
/* 2.8" ILI9341 TFT LCD 240x320 (Rotation: 0 top up, 1 left, 2 bottom, 3 right) */
#define TFT_MOSI 13
#define TFT_MISO 12
#define TFT_SCLK 14
#define TFT_CS   16
#define TFT_DC   15
#define TFT_RST  5
//#define TFT_BL NC
Arduino_DataBus *bus = new Arduino_ESP32SPI(TFT_DC /* DC */, TFT_CS /* CS */, TFT_SCLK /* SCK */, TFT_MOSI /* MOSI */, TFT_MISO /* MISO */);
Arduino_ILI9341 *gfx = new Arduino_ILI9341(bus, TFT_RST /* RST */, 0 /* rotation */, false /* IPS */);
const int BTN_A = 3;
const int BTN_B = 4;

#elif defined(ARDUINO_DEVKITV1) 
/* DOIT ESP32 DEVKIT V1 + ILI9341 */
/* 2.8" ILI9341 TFT LCD 240x320 (Rotation: 0 top up, 1 left, 2 bottom, 3 right) */
#define TFT_MOSI 23
#define TFT_MISO 19
#define TFT_SCLK 18
#define TFT_CS   5
#define TFT_DC   16
#define TFT_RST  17
//#define TFT_BL 4
Arduino_DataBus *bus = new Arduino_ESP32SPI(TFT_DC /* DC */, TFT_CS /* CS */, TFT_SCLK /* SCK */, TFT_MOSI /* MOSI */, TFT_MISO /* MISO */);
Arduino_ILI9341 *gfx = new Arduino_ILI9341(bus, TFT_RST /* RST */, 0 /* rotation */, false /* IPS */);
const int BTN_A = 13;
const int BTN_B = 15;

#endif /* not selected specific hardware */

// Sleep timer
#define uS_TO_S_FACTOR 1000000ULL  /* Conversion factor for micro seconds to seconds */
#define TIME_TO_SLEEP  1          /* Time ESP32 will go to sleep (in seconds) */
RTC_DATA_ATTR int bootCount = 0;

// Rotation & Brightness control
int rot[2] = {1, 3};
int backlight[5] = {10, 30, 60, 120, 240};
const int pwmFreq = 5000;
const int pwmResolution = 8;
const int pwmLedChannelTFT = 0;
byte a = 1;
byte b = 4;
unsigned long p = 0;
bool inv = 0;
int pressA = 0;
int pressB = 0;

String gifArray[30], randGIF_FILENAME, playFile;
int gifArraySize;
  
void listSPIFFS() {
  gifArraySize = 0;
  Serial.println("{ls /:[");
  if (SPIFFS.begin(true)) {
    File root = SPIFFS.open("/");
    File file = root.openNextFile();
    while(file){
      gifArray[gifArraySize] = String(file.name());
      Serial.println(("  {#:" + String(gifArraySize) + ", name:" + String(file.name()) + ",                     ").substring(0,40) + "\tsize:" + String(file.size()) + "}");
      file = root.openNextFile();
      gifArraySize++;
    }
    randGIF_FILENAME = gifArray[random(0, gifArraySize)];
    //p = millis();
    //randGIF_FILENAME = gifArray[millis() % gifArraySize];
    Serial.println("  {randomGIF:" + String(randGIF_FILENAME) +"}");
    Serial.println("]}");
  }
}

void eraseSPIFFS() {
  if(SPIFFS.begin(true)) {
    bool formatted = SPIFFS.format();
    if(formatted) {
      Serial.println("\n\nSuccess formatting");
      listSPIFFS();
    } else {
      Serial.println("\n\nError formatting");
    }
  }
}

void adjGIF() {
  if (digitalRead(BTN_A) == 0) {
    if (pressA == 0) {
      pressA = 1;
      inv = !inv;
      gfx->invertDisplay(inv);
      Serial.println("{BTN_A:Pressed, Inverted:" + String(inv) + "}");
      //a++;
      //if (a >= 2)
      //  a = 0;
      //gfx->setRotation(rot[a]);
      //Serial.println("{BTN_A:Pressed, Rotation:" + String(rot[a]) + "}");
      //Serial.println("{BTN_A:Pressed}");
      listSPIFFS();
    }
  } else pressA = 0;
}

void adjBrightness() {
  if (digitalRead(BTN_B) == 0) {
    if (pressB == 0) {
      pressB = 1;
      b++;
      //if (b > 15) b = 7;
      //M5.Axp.ScreenBreath(b);
      if (b >= 5) b = 0;
      ledcWrite(pwmLedChannelTFT, backlight[b]);
      Serial.println("{BTN_B:Pressed, Brightness:" + String(backlight[b]) + "}");
    }
  } else pressB = 0;
}
    
void gfxPlayGIF() {
  // Init SPIFFS
  if (!SPIFFS.begin(true)) {
    Serial.println(F("ERROR: SPIFFS mount failed!"));
    gfx->println(F("ERROR: SPIFFS mount failed!"));
  } else {

#ifndef GIF_FILENAME
#define GIF_FILENAME
    playFile = "/" + String(randGIF_FILENAME);
	  Serial.println("{Opening random GIF_FILENAME " + playFile + "}");
#else
    playFile = GIF_FILENAME;
	  Serial.println("{Opening designated GIF_FILENAME " + playFile + "}");
#endif
    
    File vFile = SPIFFS.open(playFile);
    if (!vFile || vFile.isDirectory()) {
      Serial.println(F("ERROR: Failed to open file for reading"));
      gfx->println(F("ERROR: Failed to open file for reading"));
      gfx->println(playFile);
    } else {
      gd_GIF *gif = gd_open_gif(&vFile);
      if (!gif) {
        Serial.println(F("gd_open_gif() failed!"));
      } else {
        int32_t s = gif->width * gif->height;
        uint8_t *buf = (uint8_t *)malloc(s);
        if (!buf) {
          Serial.println(F("buf malloc failed!"));
        } else {
          Serial.println(F("{acion:play, GIF:started, Info:["));
          Serial.printf("  {canvas size: %ux%u}\n", gif->width, gif->height);
          Serial.printf("  {number of colors: %d}\n", gif->palette->size);
          Serial.println(F("]}"));
          
          int t_fstart, t_delay = 0, t_real_delay, res, delay_until;
          int duration = 0, remain = 0;
//          while (1) {
            gfx->setAddrWindow((gfx->width() - gif->width) / 2, (gfx->height() - gif->height) / 2, gif->width, gif->height);
            t_fstart = millis();
            t_delay = gif->gce.delay * 10;
            res = gd_get_frame(gif, buf);
            if (res < 0) {
              Serial.println(F("ERROR: gd_get_frame() failed!"));
              break;
            } else if (res == 0) {
              Serial.printf("{action:rewind, duration:%d, remain:%d (%0.1f%%)}\n", duration, remain, 100.0 * remain / duration);
              duration = 0;
              remain = 0;
              gd_rewind(gif);
              continue;
            }
 
            gfx->startWrite();
            gfx->writeIndexedPixels(buf, gif->palette->colors, s);
            gfx->endWrite();

            t_real_delay = t_delay - (millis() - t_fstart);
            duration += t_delay;
            remain += t_real_delay;
            delay_until = millis() + t_real_delay;
            do {
              delay(1);
            } while (millis() < delay_until);

            adjBrightness();
            adjGIF();
//          }
          Serial.println(F("action:stop, GIF:ended"));
          Serial.printf("{duration: %d, remain: %d (%0.1f %%)}\n", duration, remain, 100.0 * remain / duration);
          gd_close_gif(gif);
        }
      }
    }
  }
}

unsigned long testRainbow(uint8_t cIndex) {
  gfx->fillScreen(BLACK);
  unsigned long start = micros();
  int w = gfx->width(), h = gfx->height(), s = h / 8;
  uint16_t arr [] = { PINK, RED, ORANGE, YELLOW, GREEN, MAGENTA, BLUE, WHITE, PINK, RED, ORANGE, YELLOW, GREEN, MAGENTA, BLUE, WHITE };
  gfx->fillRect(0, 0, w, s, arr [cIndex]);
  gfx->fillRect(0, s, w, 2 * s, arr [cIndex + 1]);
  gfx->fillRect(0, 2 * s, w, 3 * s, arr [cIndex + 2]);
  gfx->fillRect(0, 3 * s, w, 4 * s, arr [cIndex + 3]);
  gfx->fillRect(0, 4 * s, w, 5 * s, arr [cIndex + 4]);
  gfx->fillRect(0, 5 * s, w, 6 * s, arr [cIndex + 5]);
  gfx->fillRect(0, 6 * s, w, 7 * s, arr [cIndex + 6]);
  gfx->fillRect(0, 7 * s, w, 8 * s, arr [cIndex + 7]);
  return micros() - start;
}

unsigned long testChar(uint16_t colorT, uint16_t colorB) {
  gfx->fillScreen(colorB);
  unsigned long start = micros();
  gfx->setTextColor(GREEN);
  for (int x = 0; x < 16; x++){
    gfx->setCursor(10 + x * 8, 2);
    gfx->print(x, 16);
  }
  gfx->setTextColor(BLUE);
  for (int y = 0; y < 16; y++){
    gfx->setCursor(2, 12 + y * 10);
    gfx->print(y, 16);
  }

  char c = 0;
  for (int y = 0; y < 16; y++){
    for (int x = 0; x < 16; x++){
      gfx->drawChar(10 + x * 8, 12 + y * 10, c++, colorT, colorB);
    }
  }
  return micros() - start;
}

unsigned long testFilledCircles(uint8_t radius, uint16_t color) {
  gfx->fillScreen(BLACK);
  unsigned long start;
  int x, y, r2 = radius * 2,
    w = gfx->width(), h = gfx->height();
  start = micros();
  for(x=radius; x<w; x+=r2) {
    for(y=radius; y<h; y+=r2) {
      gfx->fillCircle(x, y, radius, color);
    }
  }
  return micros() - start;
}

unsigned long testCircles(uint8_t radius, uint16_t color) {
  // gfx->fillScreen(BLACK);
  // Screen is not cleared for this one -- this is
  // intentional and does not affect the reported time.
  unsigned long start;
  int x, y, r2 = radius * 2,
    w = gfx->width()  + radius, h = gfx->height() + radius;
  start = micros();
  for(x=0; x<w; x+=r2) {
    for(y=0; y<h; y+=r2) {
      gfx->drawCircle(x, y, radius, color);
    }
  }
  return micros() - start;
}

void screen_test() {
	Serial.print(F("Draw Ranbow: "));
  Serial.println(testRainbow(0));
  delay(500);
  Serial.print(F("Draw Ranbow: "));
  Serial.println(testRainbow(2));
  delay(500);
  Serial.print(F("Draw Ranbow: "));
  Serial.println(testRainbow(4));
  delay(500);
  Serial.print(F("Draw Ranbow: "));
  Serial.println(testRainbow(6));
  delay(500);
  Serial.print(F("Draw Filled Circles: "));
  Serial.println(testFilledCircles(10, MAGENTA));
  delay(500);
  Serial.print(F("Draw Circles: "));
  Serial.println(testCircles(10, BLACK));
  delay(500);
  Serial.print(F("Draw Filled Circles: "));
  Serial.println(testFilledCircles(10, YELLOW));
  delay(500);
  Serial.print(F("Draw Circles: "));
  Serial.println(testCircles(10, BLUE));
  delay(500);
  Serial.print(F("Draw Filled Circles: "));
  Serial.println(testFilledCircles(10, RED));
  delay(500);
  Serial.print(F("Draw Circles: "));
  Serial.println(testCircles(10, WHITE));
  delay(500);
  Serial.print(F("Draw Text: "));
  Serial.println(testChar(WHITE, BLACK));
  delay(500);
  Serial.print(F("Draw Text: "));
  Serial.println(testChar(BLUE, WHITE));
  delay(500);
  gfx->fillScreen(BLACK);
}

void setup() {
  pinMode(BTN_A, INPUT_PULLUP);
  pinMode(BTN_B, INPUT);
#if defined(ARDUINO_M5STICKC)
  M5.begin();
#endif
  Serial.begin(115200);
  delay(500);
  Serial.println("{Device:Started}");

  listSPIFFS();
  //eraseSPIFFS();
     
  // Init Video
  gfx->begin();
  gfx->fillScreen(BLACK);

  // Turn on Backlight
#ifdef TFT_BL
  //M5.Axp.ScreenBreath(b);
  ledcSetup(pwmLedChannelTFT, pwmFreq, pwmResolution); // 5 kHz PWM, 8-bit resolution
  ledcAttachPin(TFT_BL, pwmLedChannelTFT);             // assign TFT_BL pin to channel
  ledcWrite(pwmLedChannelTFT, backlight[b]);           // brightness 0 - 255
#endif

#ifdef DEBUG
  screen_test();
#endif
 
  gfxPlayGIF();

  // Turn off Backlight
#ifdef TFT_BL
  delay(60000);
  ledcDetachPin(TFT_BL);
#endif

  // Put device to sleep
  gfx->displayOff();
  ++bootCount;
  Serial.println("Boot number: " + String(bootCount));
  esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);
  Serial.flush(); 
  esp_deep_sleep_start();
}

void loop() {
}

gifdec.cpp

Arduino
/*
 * rewrite from: https://github.com/BasementCat/arduino-tft-gif
*/
#include "gifdec.h"
#include <sys/types.h>
#include <FS.h>

#define MIN(A, B) ((A) < (B) ? (A) : (B))
#define MAX(A, B) ((A) > (B) ? (A) : (B))

int gif_buf_last_idx, gif_buf_idx, file_pos;
uint8_t gif_buf[GIF_BUF_SIZE];

static bool gif_buf_seek(File *fd, int len)
{
    if (len > (gif_buf_last_idx - gif_buf_idx))
    {
        fd->seek(len - (gif_buf_last_idx - gif_buf_idx), SeekCur);
        gif_buf_idx = gif_buf_last_idx;
    }
    else
    {
        gif_buf_idx += len;
    }
    return true;
}

static int gif_buf_read(File *fd, uint8_t *dest, int 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;
}

static 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++];
}

static uint16_t gif_buf_read16(File *fd)
{
    return gif_buf_read(fd) + (((uint16_t)gif_buf_read(fd)) << 8);
}

static 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);
    }
}

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;
}

static 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);
}

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;
        off_t sub_block;
        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);
}

static 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);
}

static void read_comment_ext(gd_GIF *gif)
{
    if (gif->comment)
    {
        gif->comment(gif);
    }
    /* 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];

    /* 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);
    }
}

static 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);
    }
}

static gd_Table *new_table()
{
    // int key;
    // int 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;
    int s = sizeof(gd_Table) + (sizeof(gd_Entry) * 4096);
    gd_Table *table = (gd_Table *)malloc(s);
    if (table)
    {
        Serial.printf("new_table() malloc %d.\n", s);
    }
    else
    {
        Serial.printf("new_table() malloc %d failed!\n", s);
    }
    table->entries = (gd_Entry *)&table[1];
    return table;
}

static void reset_table(gd_Table *table, int32_t key_size)
{
    table->nentries = (1 << key_size) + 2;
    for (int32_t key = 0; key < (1 << key_size); key++)
    {
        table->entries[key] = (gd_Entry){1, 0xFFF, 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 */
static 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;
}

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)
                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. */
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). */
static int32_t read_image_data(gd_GIF *gif, int interlace, uint8_t *frame)
{
    uint8_t sub_len, shift, byte;
    int init_key_size, key_size, table_is_full;
    int frm_off, str_len, p, x, y;
    uint16_t key, clear, stop;
    int32_t ret;
    gd_Entry entry;
    off_t start, end;

    // Serial.println("Read key size");
    gif_buf_read(gif->fd, &byte, 1);
    key_size = (int)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((int)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). */
static int32_t read_image(gd_GIF *gif, uint8_t *frame)
{
    uint8_t fisrz;
    int 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);
}

static void render_frame_rect(gd_GIF *gif, uint16_t *buffer, uint8_t *frame)
{
    int i, j, k;
    uint8_t index, *color;
    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;
    }
}

/* 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.printf("Read sep: [%d].\n", sep);
            return -1;
        }
    }
    // Serial.println("Do read image");
    if (read_image(gif, frame) == -1)
        return -1;
    return 1;
}

void gd_rewind(gd_GIF *gif)
{
    gif->fd->seek(gif->anim_start, SeekSet);
    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);
}

gifdec.h

Arduino
GIF file decoder
/*
 * rewrite from: https://github.com/BasementCat/arduino-tft-gif
*/
#ifndef _GIFDEC_H_
#define _GIFDEC_H_

#include <FS.h>

#define GIF_BUF_SIZE 1024

typedef struct gd_Palette {
    int size;
    uint16_t colors[256];
} gd_Palette;

typedef struct gd_GCE {
    uint16_t delay;
    uint8_t tindex;
    uint8_t disposal;
    int input;
    int transparency;
} gd_GCE;

typedef struct gd_Entry {
    int32_t length;
    uint16_t prefix;
    uint8_t  suffix;
} gd_Entry;

typedef struct gd_Table {
    int bulk;
    int 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;

gd_GIF *gd_open_gif(File* fd);
static gd_Table * new_table();
static void reset_table(gd_Table* table, int key_size);
// int32_t add_entry(gd_Table* table, int32_t length, uint16_t prefix, uint8_t suffix)
int32_t gd_get_frame(gd_GIF *gif, uint8_t *frame);
void gd_rewind(gd_GIF *gif);
void gd_close_gif(gd_GIF *gif);

#endif /* _GIFDEC_H_ */

LibraryDebugger.ino

Arduino
Library Debugger to test which version of Arduino_GFX_Library works for you.
/* 
 * Arduino_GFX_Library Library Debugger
 * Author: tommyho510@gmail.com
 * Background: Arduino_ESP32SPI_DMA class obsoleted on Apr 4, 2021. Some users may have issue compiling espgfxGIF.ino 
 *             because of mixing up different codes and library.
 * Usage: Compile this file to see if the installed libraries are compatible with the main script
 * 
 * Adafruit GFX Library 1.10.12
 * Arduino_GFX_Library 1.0.6 - passed OBS
 * Arduino_GFX_Library 1.1.2 - passed
 * Arduino_GFX_Library 1.1.3 - passed
 * Arduino_GFX_Library 1.1.4 - failed
 * Arduino_GFX_Library 1.1.5 - failed
 * Arduino_GFX_Library 1.1.6 - failed
 * 
 */

/* PICK YOU DEBUG BOARD */ 

//#define ARDUNIO_GENERIC_HW
//#define ARDUINO_M5CORE2
#define ARDUINO_M5STICKC
//#define ARDUINO_TDISPLAY
//#define ARDUINO_M5STICKC_OBS
//#define ARDUINO_TDISPLAY_OBS

/* DO NOT EDIT BELOW */
#include <Arduino_GFX_Library.h>

/* M5Stack Core2
 * 2.00" ILI9342 TFT LCD 320x240 Core2 * (Rotation: 0 bottom up, 1 right, 2 top, 3 left
 */
#if defined(ARDUINO_GENERIC_HW)
#define TFT_BL 2
static Arduino_DataBus *bus = new Arduino_HWSPI(27 /* DC */, 5 /* CS */);
static Arduino_GFX *gfx = new Arduino_ST7735(bus, 18 /* RST */, 1 /* rotation */, true /* IPS */, 80 /* width */, 160 /* height */, 26 /* col offset 1 */, 1 /* row offset 1 */, 26 /* col offset 2 */, 1 /* row offset 2 */);

/* M5Stack Core2
 * 2.00" ILI9342 TFT LCD 320x240 Core2 * (Rotation: 0 bottom up, 1 right, 2 top, 3 left
 */
#elif defined(ARDUINO_M5CORE2)
#include <M5Core2.h>
#define TFT_BL 32
static Arduino_DataBus *bus = new Arduino_ESP32SPI(27 /* DC */, 14 /* CS */, SCK, MOSI, MISO);
static Arduino_GFX *gfx = new Arduino_ILI9342(bus, 33 /* RST */, 1 /* rotation */);

/* M5Stack M5StickC
 * 0.96" ST7735 IPS LCD 80x160 M5Stick-C * (Rotation: 0 bottom up, 1 right, 2 top, 3 left)
 */
#elif defined(ARDUINO_M5STICKC)
#include <M5StickC.h>
#define TFT_BL 2
static Arduino_DataBus *bus = new Arduino_ESP32SPI(27 /* DC */, 5 /* CS */, 13 /* SCK */, 15 /* MOSI */, -1 /* MISO */);
static Arduino_GFX *gfx = new Arduino_ST7735(bus, 18 /* RST */, 1 /* rotation */, true /* IPS */, 80 /* width */, 160 /* height */, 26 /* col offset 1 */, 1 /* row offset 1 */, 26 /* col offset 2 */, 1 /* row offset 2 */);

/* TTGO T-DISPLAY
 * 1.14" ST7789 IPS LCD 135x240 TTGO T-Display (Rotation: 0 top up, 1 left, 2 bottom, 3 right)
 */
#elif defined(ARDUINO_TDISPLAY)
#define TFT_BL 4
static Arduino_DataBus *bus = new Arduino_ESP32SPI(16 /* DC */, 5 /* CS */, 18 /* SCK */, 19 /* MOSI */, -1 /* MISO */);
static Arduino_GFX *gfx = new Arduino_ST7789(bus, 23 /* RST */, 2 /* rotation */, true /* IPS */, 135 /* width */, 240 /* height */, 53 /* col offset 1 */, 40 /* row offset 1 */, 52 /* col offset 2 */, 40 /* row offset 2 */);

/* M5Stack M5StickC with OBS GFX Lib V1.0.6
 * 0.96" ST7735 IPS LCD 80x160 M5Stick-C * (Rotation: 0 bottom up, 1 right, 2 top, 3 left)
 */
#elif defined(ARDUINO_M5STICKC_OBS)
#include <M5StickC.h>
#define TFT_BL 2
Arduino_ESP32SPI_DMA *bus = new Arduino_ESP32SPI_DMA(23 /* DC */, 5 /* CS */, 13 /* SCK */, 15 /* MOSI */, -1 /* MISO */);
Arduino_ST7735 *gfx = new Arduino_ST7735(bus, 18 /* RST */, 1 /* rotation */, true /* IPS */, 80 /* width */, 160 /* height */, 26 /* col offset 1 */, 1 /* row offset 1 */, 26 /* col offset 2 */, 1 /* row offset 2 */);

/* TTGO T-DISPLAY with OBS GFX Lib V1.0.6
 * 0.96" ST7735 IPS LCD 80x160 M5Stick-C * (Rotation: 0 bottom up, 1 right, 2 top, 3 left)
 */
#elif defined(ARDUINO_TDISPLAY_OBS)
#define TFT_BL 4
Arduino_ESP32SPI_DMA *bus = new Arduino_ESP32SPI_DMA(16 /* DC */, 5 /* CS */, 18 /* SCK */, 19 /* MOSI */, -1 /* MISO */);
Arduino_ST7789 *gfx = new Arduino_ST7789(bus, 23 /* RST */, 2 /* rotation */, true /* IPS */, 135 /* width */, 240 /* height */, 53 /* col offset 1 */, 40 /* row offset 1 */, 52 /* col offset 2 */, 40 /* row offset 2 */);

#endif

void setup() {
  // put your setup code here, to run once:

}

void loop() {
  // put your main code here, to run repeatedly:

}

espgfxGIF

github link

Credits

tommyho

tommyho

7 projects • 11 followers
Manufacturing Engineer for Robotics products
陳亮

陳亮

3 projects • 3 followers
Thanks to moononournation.

Comments

Add projectSign up / Login