tommyho陳亮
Published © Apache-2.0

Arduino Animated GIF Player

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

BeginnerFull instructions provided1 hour21,913

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
Version 2023.12A
Please note this is only one of the complete set of files. You must have all the files in the folder to work. See Instructions.
/*
 *  espgfxGIF Version 2023.12A (Brightness Edition)
 *  Board: T-Display S3, T-Display, M5StickC-Plus, M5StickC (esp32)
 *  Author: tommyho510@gmail.com
 *  Adapted from: moononournation
 *  Project details: https://github.com/tommykho/IOT-cookbook https://www.hackster.io/tommyho/arduino-animated-gif-player-8964df
 *  Required: Arduino library Arduino_GFX 1.3.7
 *  Dependency: gifdec.h
 *  IOT-cookbook Helpers: spiffs, gfx
 *
 *  Please upload SPIFFS data with ESP32 Sketch Data Upload:
 *  https://github.com/me-no-dev/arduino-esp32fs-plugin
 *  GIF src: various
 */

#include <Arduino_GFX_Library.h>  /* Install via Arduino Library Manager */

// *** BEGIN editing of your settings ...
#define ARDUINO_M5STICKCPLUS
//#define GIF_FILENAME "/your_file.gif" /* comment out for random GIF */
//#define DEBUG /* uncomment this line to start with screen test */
// *** END editing of your settings ...

#if defined(ARDUINO_M5STICKCPLUS)
/* M5Stack */
/* 1.14" ST7789 IPS LCD 135x240 M5StickC Plus * (Rotation: 0 bottom up, 1 right, 2 top, 3 left) */
#define TFT_MOSI 15
#define TFT_SCLK 13
#define TFT_CS   5
#define TFT_DC   23
#define TFT_RST  18
#define LED      10
#define TFT_BL   2
Arduino_DataBus *bus = new Arduino_ESP32SPI(TFT_DC /* DC */, TFT_CS /* CS */, TFT_SCLK /* SCK */, TFT_MOSI /* MOSI */, -1 /* MISO */);
Arduino_ST7789 *gfx = new Arduino_ST7789(bus, TFT_RST /* RST */, 1 /* rotation */, true /* IPS */, 135 /* width */, 240 /* height */,
                      53 /* col offset 1 */, 40 /* row offset 1 */, 52 /* col offset 2 */, 40 /* row offset 2 */);
const int BTN_A = 37;
const int BTN_B = 39;
#include <M5StickCPlus.h>

#elif defined(ARDUINO_M5STICKC) 
/* M5Stack */
/* 0.96" ST7735 IPS LCD 80x160 M5StickC * (Rotation: 0 bottom up, 1 right, 2 top, 3 left) */
#define TFT_MOSI 15
#define TFT_SCLK 13
#define TFT_CS   5
#define TFT_DC   23
#define TFT_RST  18
#define LED      10
#define TFT_BL   2
Arduino_DataBus *bus = new Arduino_ESP32SPI(TFT_DC /* DC */, TFT_CS /* CS */, TFT_SCLK /* SCK */, TFT_MOSI /* MOSI */, -1 /* MISO */);
Arduino_ST7735 *gfx = new Arduino_ST7735(bus, TFT_RST /* 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 */);
const int BTN_A = 37;
const int BTN_B = 39;
#include <M5StickC.h>

#elif defined(ARDUINO_TDISPLAYS3)
/* LILYGO T-Display */
/* 1.90" ST7789V IPS LCD 170x320 TTGO T-Display (Rotation: 0 bottom, 1 left, 2 top, 3 right) */
#define TFT_CS   6
#define TFT_DC   7
#define TFT_RST  5
#define TFT_BL   38
#define LED      2
Arduino_DataBus *bus = new Arduino_ESP32LCD8(TFT_DC /* DC */, TFT_CS /* CS */, 8 /* WR */, 9 /* RD */, 
					39 /* D0 */, 40 /* D1 */, 41 /* D2 */, 42 /* D3 */,
					45 /* D4 */, 46 /* D5 */, 47 /* D6 */, 48 /* D7 */);
Arduino_GFX *gfx = new Arduino_ST7789(bus, TFT_RST /* RST */, 0 /* rotation */, true /* IPS */, 170 /* width */, 320 /* height */,
					35 /* col offset 1 */, 0 /* row offset 1 */, 35 /* col offset 2 */, 0 /* row offset 2 */);
const int BTN_A = 0;
const int BTN_B = 14;

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


// Load IOT-cookbook helpers
#include "gifdec.h"  
#include "spiffs-helper.h"
#include "gfx-helper.h"
#include "led-helper.h"

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

String gifArray[30], randGIF_FILENAME, playFile, gifFile;
int gifArraySize;
File vFile;

// Main subroutine  
void gfxPlayGIF() {
#if defined(_SPIFFS_H)
  loadSPIFFS();
#endif

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

void setup() {
  Serial.begin(115200);
  delay(500);

  pinMode(BTN_A, INPUT_PULLUP);
  pinMode(BTN_B, INPUT);

  Serial.println("{Device:Started}");
#if defined(ARDUINO_M5STICKC)
  M5.begin();
#endif
#if defined(ARDUINO_M5STICKCPLUS)
  M5.begin();
  M5.Beep.tone(4000);
  delay(250);
  M5.Beep.mute();
#endif

#if defined(_LED_H)
  ledTimer();
#endif

#if defined(_SPIFFS_H)
  listSPIFFS();
  //eraseSPIFFS();
#endif
     
  // 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
  gfxScreenTest();
#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() {
}

espgfxGIF-dstarc.ino

Arduino
A custom script for espgfxGIF.ino for
D1MINI + ST7789
#define TFT_MOSI 13
#define TFT_MISO 12
#define TFT_SCLK 7
#define TFT_CS 16
#define TFT_DC 4
#define TFT_RST 0
const int BTN_A = 3;
const int BTN_B = 4;
#include <SPIFFS.h>
#include <Arduino_GFX_Library.h>  /* Install via Arduino Library Manager */
#include "gifdec.h"

/*
 *  espgfxGIF Version 2022.05A (Brightness Edition)
 *  Board: TTGO T-Display & M5Stack M5StickC (esp32)
 *  Author: tommyho510@gmail.com
 *  Original Author: moononournation
 *  Required: Arduino library Arduino_GFX 1.2.0
 *  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_M5STICKC
#define ARDUINO_D1MINI_ST7789
#define GIF_FILENAME "/pcboot.gif" /* comment out for random GIF */
// *** END editing of your settings ...

#if defined(ARDUINO_D1MINI_ST7789) 
/* LOLIN D1 Mini esp8266 + TFT Shield */
/* 1.54" ST7789 TFT LCD 240x240 (Rotation: 0 top up, 1 left, 2 bottom, 3 right) */
#define TFT_MOSI 13
#define TFT_MISO 12
#define TFT_SCLK 7
#define TFT_CS   16
#define TFT_DC   4
#define TFT_RST  0
//#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_ST7789 *gfx = new Arduino_ST7789(bus, TFT_RST /* RST */, 1 /* rotation */, true /* IPS */, 240 /* width */, 240 /* height */, 52 /* col offset 1 */, 40 /* row offset 1 */, 53 /* col offset 2 */, 40 /* row offset 2 */);
const int BTN_A = 3;
const int BTN_B = 4;

#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 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 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 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 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);
#else
playFile = GIF_FILENAME;
#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);
        }
      }
    }
  }
}

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

  gfxPlayGIF();

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

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

void loop() {
}

espgfxGIF_Timer.ino

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

/*
 *  espgfxGIF Version 2023.08B (Brightness Edition)
 *  Board: T-Display S3, T-Display, M5StickC-Plus, M5StickC (esp32)
 *  Author: tommyho510@gmail.com
 *  Original Author: moononournation
 *  Required: Arduino library Arduino_GFX 1.3.7
 *  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_M5STICKCPLUS
//#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_M5STICKCPLUS)
/* M5Stack */
/* 1.14" ST7789 IPS LCD 135x240 M5StickC Plus * (Rotation: 0 bottom up, 1 right, 2 top, 3 left) */
#define TFT_MOSI 15
#define TFT_SCLK 13
#define TFT_CS   5
#define TFT_DC   23
#define TFT_RST  18
#define LED      10
#define TFT_BL   2
Arduino_DataBus *bus = new Arduino_ESP32SPI(TFT_DC /* DC */, TFT_CS /* CS */, TFT_SCLK /* SCK */, TFT_MOSI /* MOSI */, -1 /* MISO */);
Arduino_ST7789 *gfx = new Arduino_ST7789(bus, TFT_RST /* RST */, 1 /* rotation */, true /* IPS */, 135 /* width */, 240 /* height */,
                      53 /* col offset 1 */, 40 /* row offset 1 */, 52 /* col offset 2 */, 40 /* row offset 2 */);
const int BTN_A = 37;
const int BTN_B = 39;
#include <M5StickCPlus.h>

#elif defined(ARDUINO_M5STICKC) 
/* M5Stack */
/* 0.96" ST7735 IPS LCD 80x160 M5StickC * (Rotation: 0 bottom up, 1 right, 2 top, 3 left) */
#define TFT_MOSI 15
#define TFT_SCLK 13
#define TFT_CS   5
#define TFT_DC   23
#define TFT_RST  18
#define LED      10
#define TFT_BL   2
Arduino_DataBus *bus = new Arduino_ESP32SPI(TFT_DC /* DC */, TFT_CS /* CS */, TFT_SCLK /* SCK */, TFT_MOSI /* MOSI */, -1 /* MISO */);
Arduino_ST7735 *gfx = new Arduino_ST7735(bus, TFT_RST /* 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 */);
const int BTN_A = 37;
const int BTN_B = 39;
#include <M5StickC.h>

#elif defined(ARDUINO_TDISPLAYS3)
/* LILYGO T-Display */
/* 1.90" ST7789V IPS LCD 170x320 TTGO T-Display (Rotation: 0 bottom, 1 left, 2 top, 3 right) */
#define TFT_CS   6
#define TFT_DC   7
#define TFT_RST  5
#define TFT_BL   38
#define LED      2
Arduino_DataBus *bus = new Arduino_ESP32LCD8(TFT_DC /* DC */, TFT_CS /* CS */, 8 /* WR */, 9 /* RD */, 
					39 /* D0 */, 40 /* D1 */, 41 /* D2 */, 42 /* D3 */,
					45 /* D4 */, 46 /* D5 */, 47 /* D6 */, 48 /* D7 */);
Arduino_GFX *gfx = new Arduino_ST7789(bus, TFT_RST /* RST */, 0 /* rotation */, true /* IPS */, 170 /* width */, 320 /* height */,
					35 /* col offset 1 */, 0 /* row offset 1 */, 35 /* col offset 2 */, 0 /* row offset 2 */);
const int BTN_A = 0;
const int BTN_B = 14;

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

#include "gifdec.h"  
#include "spiffs-helper.h"
#include "gfx-helper.h"

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

void setup() {
  Serial.begin(115200);
  delay(500);

  pinMode(BTN_A, INPUT_PULLUP);
  pinMode(BTN_B, INPUT);

  Serial.println("{Device:Started}");
#if defined(ARDUINO_M5STICKC)
  M5.begin();
#endif
#if defined(ARDUINO_M5STICKCPLUS)
  M5.begin();
#endif

  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
  gfxScreenTest();
#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
/*
 * Adapted from: https://github.com/BasementCat/arduino-tft-gif
 * Project details: https://github.com/tommykho/IOT-cookbook https://www.hackster.io/tommyho/arduino-animated-gif-player-8964df
*/

#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
/*
 * Adapted from: https://github.com/BasementCat/arduino-tft-gif
 * Project details: https://github.com/tommykho/IOT-cookbook https://www.hackster.io/tommyho/arduino-animated-gif-player-8964df
*/

#include <FS.h>
#ifndef _GIFDEC_H_
#define _GIFDEC_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_ */

spiffs-helper.h

Arduino
helper file for SPIFFS
/*
 * Author: tommyho510@gmail.com
 * Project details: https://github.com/tommykho/IOT-cookbook https://www.hackster.io/tommyho/arduino-animated-gif-player-8964df
*/

#include <SPIFFS.h>
#ifndef _SPIFFS_H
#define _SPIFFS_H

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 loadSPIFFS() {
  // 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
    vFile = SPIFFS.open(playFile);
  }
}

#endif /* _SPIFFS_H */

gfx-helper.h

Arduino
helper file for GFX
/*
 * Author: tommyho510@gmail.com
 * Project details: https://github.com/tommykho/IOT-cookbook https://www.hackster.io/tommyho/arduino-animated-gif-player-8964df
*/

#include <Arduino_GFX_Library.h>  /* Install via Arduino Library Manager */
#ifndef _Arduino_GFX_H
#define _Arduino_GFX_H

// Press BTN_A to invert display
void adjGIF() {
  if (digitalRead(BTN_A) == 0) {
    if (pressA == 0) {
#if defined(ARDUINO_M5STICKCPLUS)
      M5.Beep.tone(3000);
      delay(250);
      M5.Beep.mute();
#endif
      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;
    M5.Beep.mute();
  }
}

// Press BTN_B to adjust brightness
void adjBrightness() {
  if (digitalRead(BTN_B) == 0) {
    if (pressB == 0) {
      b++;
      if (b >= 5) b = 0; 

#if defined(ARDUINO_M5STICKCPLUS)
      if (b == 4) {
        M5.Beep.tone(2000);
        delay(250);
      } 
      M5.Beep.tone(3000);
      delay(250);
      M5.Beep.mute();
      M5.Axp.ScreenBreath(axp[b]);
#endif

#if defined(ARDUINO_M5STICKC)
      M5.Axp.ScreenBreath(axp[b]);
#endif

      ledcWrite(pwmLedChannelTFT, backlight[b]);
      Serial.println("{BTN_B:Pressed, Brightness:" + String(backlight[b]) + "}");

      pressB = 1;
    }
  } else {
    pressB = 0;
    M5.Beep.mute();
  }
}

unsigned long gfxRainbow(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 gfxChar(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 gfxFilledCircles(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 gfxCircles(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 gfxScreenTest() {
	Serial.print(F("Draw Ranbow: "));
  Serial.println(gfxRainbow(0));
  delay(500);
  Serial.print(F("Draw Ranbow: "));
  Serial.println(gfxRainbow(2));
  delay(500);
  Serial.print(F("Draw Ranbow: "));
  Serial.println(gfxRainbow(4));
  delay(500);
  Serial.print(F("Draw Ranbow: "));
  Serial.println(gfxRainbow(6));
  delay(500);
  Serial.print(F("Draw Filled Circles: "));
  Serial.println(gfxFilledCircles(10, MAGENTA));
  delay(500);
  Serial.print(F("Draw Circles: "));
  Serial.println(gfxCircles(10, BLACK));
  delay(500);
  Serial.print(F("Draw Filled Circles: "));
  Serial.println(gfxFilledCircles(10, YELLOW));
  delay(500);
  Serial.print(F("Draw Circles: "));
  Serial.println(gfxCircles(10, BLUE));
  delay(500);
  Serial.print(F("Draw Filled Circles: "));
  Serial.println(gfxFilledCircles(10, RED));
  delay(500);
  Serial.print(F("Draw Circles: "));
  Serial.println(gfxCircles(10, WHITE));
  delay(500);
  Serial.print(F("Draw Text: "));
  Serial.println(gfxChar(WHITE, BLACK));
  delay(500);
  Serial.print(F("Draw Text: "));
  Serial.println(gfxChar(BLUE, WHITE));
  delay(500);
  gfx->fillScreen(BLACK);
}

#endif /* _Arduino_GFX_H */

led-helper.h

Arduino
Helper file for blinking Built-in LED
/*
 * Author: tommyho510@gmail.com
 * Project details: https://github.com/tommykho/IOT-cookbook https://www.hackster.io/tommyho/arduino-animated-gif-player-8964df
*/

#ifndef _LED_H
#define _LED_H

#ifndef LED
#define LED      2
#endif

hw_timer_t *timer = NULL;
volatile SemaphoreHandle_t timerSemaphore;
portMUX_TYPE timerMux       = portMUX_INITIALIZER_UNLOCKED;
volatile uint8_t TimerCount = 0;

void IRAM_ATTR onTimer() {
    portENTER_CRITICAL_ISR(&timerMux);
    digitalWrite(LED, TimerCount % 100);
    TimerCount++;
    portEXIT_CRITICAL_ISR(&timerMux);
}

void ledTimer() {
  pinMode(LED, OUTPUT);
  timerSemaphore = xSemaphoreCreateBinary();
  timer          = timerBegin(0, 80, true);
  timerAttachInterrupt(timer, &onTimer, true);
  timerAlarmWrite(timer, 50000, true);
  timerAlarmEnable(timer);
}

#endif /* _LED_H */

Credits

tommyho

tommyho

8 projects • 16 followers
Manufacturing Engineer for Robotics products
陳亮

陳亮

3 projects • 5 followers
Thanks to moononournation.

Comments