Giter VIP home page Giter VIP logo

animatedgif's Introduction

Welcome to my open source libraries. The aim of my efforts is to bring speed and unexpected features to all devices, big and small.

Larry's GitHub stats

animatedgif's People

Contributors

bitbank2 avatar bodmer avatar hades32 avatar ladyada avatar slav-at-attachix avatar tobozo avatar welshcoder avatar witnessmenow avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

animatedgif's Issues

How to limit Height ou reduce memory usage on ESP32?

Hello,

Your library displays GIFs perfectly on a double RGB Led matrix (128 x 32) controlled by an ESP32
But as I load a large code and many libraries (server, clock, audio spectrum, etc.), my memory space is very limited.

My problem :
With #define MAX_WIDTH 320, I cannot compile my code (overflowed by XXXX bytes)
With #define MAX_WIDTH 128 instead of 320, it's better, but a few bytes of memory are still missing to compile.

If I halve #define MAX_WIDTH 64, the code compiles fine and the images display fine. Even the 128px wide one (I understand that it doesn't cut out the images).

As I read your comment: Designed to decode images up to 480x320
I would like to know how to adjust these two values WIDTH an HEIGHT (480x320) to the actual size of my matrix (128x32 or 128x64)?
Or limit the memory for these values?

Thank you so much.

Disposal Isn’t Handled Correctly But Maybe That’s Okay

Digging into why that one Krampus animation was leaving behind pixel filth, learned some stuff about GIF disposal along the way, and that dealing with it correctly while processing line-at-a-time might be so horrible that the best solution might just be “it’s not 100% compatible with all GIFs” and leave it at that.

krampus-anim

Also managed to produce a second GIF exhibiting the issue.

animgif

What these two have in common is they’re transparent animated GIFs. The background is reset to “transparent” after each frame…and that gets into Disposal Method 3 levels of impractical.

transparent-bg-1
transparent-bg-2

Okay. But…even if an opaque background color were forced, the DM2 implementation isn’t quite right. It doesn’t manifest on most GIFs because most save/convert/optimize programs refresh the whole bounding rect to paint over old and issue new graphics each frame. Producing a truly optimal DM2 GIF is a super fussy thing that most programs miss but I managed to find a non-transparent example in the ImageMagick documentation (compare this in-browser and with the GIF code):

optframe_bgnd

(The blue rects left behind in-browser are intentional, a product of the disposal method and background color. The GIF decoder leaves each new bitmap behind, not disposing them.)

The issue, mentioned in passing deep in the ImageMagick docs, is that the disposal area is supposed to be cleared AFTER the current frame…but, as written, it’s disposing the new area first. It’s usually, but not always, mostly or entirely overlapping the same area, so it usually mostly looks correct.

https://www.imagemagick.org/Usage/anim_basics/#dispose

Doing a True and Proper Disposal™ would first require keeping track of the prior frame's disposal bounds independent of the current frame…and then, disposing the old rect while drawing the new rect in the same pass. It gets into a LOT of ugly cases…

dispose-a

I mean, it’s certainly possible as a scanline algorithm, it just hurts my brain trying to work through an efficient implementation of all the various cases, when some days I can’t even remember why I went into the kitchen…

dispose-b

Hence maybe it’s just left as a “known issue” and closed, since these GIFs seem relatively rare, and some could probably be processed with a pass through ImageMagick or Photoshop or something to do the full-rect repaint, though the resulting GIF will be a little bigger.

Add an option to not loop whole gif

I'm doing a "clock" with an ESP32 and Matrix Panel.
I don't want to cut animation by duration, I just want that they play only once.

This is what I have add to your code:
AnimatedGIF.h

public:
  void enableLoop(bool enable);

private:
  bool loopEnable = true;

AnimatedGIF.cpp
in playFrame()

if (_gif.GIFFile.iPos >= _gif.GIFFile.iSize - 1) // no more data exists
{
    if (!loopEnable)
        return 0;
    (*_gif.pfnSeek)(&_gif.GIFFile, 0); // seek to start
}

If you think it is ok, it will be cool for me if you add it to the library. But I can understand that you do not add my modifications to your code.

Thanks for your library!

1.3.x uses too much RAM to work on SAMD21

A demo by Mirko Pacioni used to compile fine on SAMD21 until 1.2.0.
I have noticed you considerably upped the buffers on 1.3.0

1.2.0

#define LZW_BUF_SIZE (6*MAX_CHUNK_SIZE)
#define LZW_HIGHWATER (4*MAX_CHUNK_SIZE)
#define MAX_WIDTH 320
#define FILE_BUF_SIZE 4096

1.3.0

#define LZW_BUF_SIZE (12*MAX_CHUNK_SIZE)
#define LZW_HIGHWATER (10*MAX_CHUNK_SIZE)
#define MAX_WIDTH 320
#define FILE_BUF_SIZE 8192

It would be nice to handle these things gracefully if you want to still support the SAMD21 :)

Thank you, mate :)

gif.open argument error

The following error occurs and it cannot be executed.
The gif.open method seems to be in error, do you know the cause?
thank you.

Error statement

In file included from /Users//Documents/Arduino/libraries/ESP32_Chimera_Core-1.0.3/src/M5Stack.h:122:0,
from /Users/
/Documents/Arduino/libraries/ESP32_Chimera_Core-1.0.3/src/ESP32-Chimera-Core.h:2,
from /Users//Documents/Arduino/AnimatedGIF/examples/ESP32-LGFX-SDCard-GifPlayer/ESP32-LGFX-SDCard-GifPlayer.ino:1:
/Users/
/Documents/Arduino/libraries/ESP32_Chimera_Core-1.0.3/src/M5Display.h: In member function 'void M5Display::writePixels(uint16_t*, uint32_t)':
/Users//Documents/Arduino/libraries/ESP32_Chimera_Core-1.0.3/src/M5Display.h:50:35: warning: 'void lgfx::LGFXBase::pushColors(T*, int32_t, bool) [with T = short unsigned int; int32_t = int]' is deprecated: use pushPixels [-Wdeprecated-declarations]
pushColors(colors, len, true);
^
In file included from /Users/
/Documents/Arduino/libraries/LovyanGFX/src/LovyanGFX.hpp:43:0,
from /Users//Documents/Arduino/libraries/LovyanGFX/src/LGFX_TFT_eSPI.hpp:14,
from /Users/
/Documents/Arduino/libraries/ESP32_Chimera_Core-1.0.3/src/M5Display.h:10,
from /Users//Documents/Arduino/libraries/ESP32_Chimera_Core-1.0.3/src/M5Stack.h:122,
from /Users/
/Documents/Arduino/libraries/ESP32_Chimera_Core-1.0.3/src/ESP32-Chimera-Core.h:2,
from /Users//Documents/Arduino/AnimatedGIF/examples/ESP32-LGFX-SDCard-GifPlayer/ESP32-LGFX-SDCard-GifPlayer.ino:1:
/Users/
/Documents/Arduino/libraries/LovyanGFX/src/lgfx/LGFXBase.hpp:481:43: note: declared here
[[deprecated("use pushPixels")]] void pushColors(T* data, std::int32_t len, bool swap) { startWrite(); writePixels(data, len, swap); endWrite(); }
^
/Users//Documents/Arduino/AnimatedGIF/examples/ESP32-LGFX-SDCard-GifPlayer/ESP32-LGFX-SDCard-GifPlayer.ino: In function 'int gifPlay(char*)':
ESP32-LGFX-SDCard-GifPlayer:162:89: error: invalid conversion from 'void* ()(char, int32_t*) {aka void* ()(char, int*)}' to 'void* ()(const char, int32_t*) {aka void* ()(const char, int*)}' [-fpermissive]
if( ! gif.open( gifPath, GIFOpenFile, GIFCloseFile, GIFReadFile, GIFSeekFile, GIFDraw ) ) {
^
In file included from /Users/
/Documents/Arduino/AnimatedGIF/examples/ESP32-LGFX-SDCard-GifPlayer/ESP32-LGFX-SDCard-GifPlayer.ino:10:0:
/var/folders/n5/ddy9p4_d5k75mfsktms5jvhw0000gp/T/arduino_build_644108/sketch/AnimatedGIF.h:138:9: note: initializing argument 2 of 'int AnimatedGIF::open(const char*, void* ()(const char, int32_t*), void ()(void), int32_t ()(GIFFILE, uint8_t*, int32_t), int32_t ()(GIFFILE, int32_t), void ()(GIFDRAW))'
int open(const char szFilename, GIF_OPEN_CALLBACK pfnOpen, GIF_CLOSE_CALLBACK pfnClose, GIF_READ_CALLBACK pfnRead, GIF_SEEK_CALLBACK pfnSeek, GIF_DRAW_CALLBACK pfnDraw);
^
/Users//Documents/Arduino/AnimatedGIF/examples/ESP32-LGFX-SDCard-GifPlayer/ESP32-LGFX-SDCard-GifPlayer.ino: At global scope:
/Users/
/Documents/Arduino/AnimatedGIF/examples/ESP32-LGFX-SDCard-GifPlayer/ESP32-LGFX-SDCard-GifPlayer.ino:35:13: warning: 'void MyCustomDelay(long unsigned int)' defined but not used [-Wunused-function]
static void MyCustomDelay( unsigned long ms ) {
^
「SD.h」に対して複数のライブラリが見つかりました
使用済:/Users//Library/Arduino15/packages/esp32/hardware/esp32/1.0.4/libraries/SD
未使用:/private/var/folders/n5/ddy9p4_d5k75mfsktms5jvhw0000gp/T/AppTranslocation/7C0A7034-DF5A-4B87-9117-0AE54FB0BF2B/d/Arduino.app/Contents/Java/libraries/SD
次のフォルダのライブラリESP32_Chimera_Core-1.0.3バージョン1.0.3を使用中:/Users/
/Documents/Arduino/libraries/ESP32_Chimera_Core-1.0.3
次のフォルダのライブラリWireバージョン1.0.1を使用中:/Users//Library/Arduino15/packages/esp32/hardware/esp32/1.0.4/libraries/Wire
次のフォルダのライブラリSPIバージョン1.0を使用中:/Users/
/Library/Arduino15/packages/esp32/hardware/esp32/1.0.4/libraries/SPI
次のフォルダのライブラリFSバージョン1.0を使用中:/Users//Library/Arduino15/packages/esp32/hardware/esp32/1.0.4/libraries/FS
次のフォルダのライブラリSD_MMCバージョン1.0を使用中:/Users/
/Library/Arduino15/packages/esp32/hardware/esp32/1.0.4/libraries/SD_MMC
次のフォルダのライブラリSPIFFSバージョン1.0を使用中:/Users//Library/Arduino15/packages/esp32/hardware/esp32/1.0.4/libraries/SPIFFS
次のフォルダのライブラリSDバージョン1.0.5を使用中:/Users/
/Library/Arduino15/packages/esp32/hardware/esp32/1.0.4/libraries/SD
次のフォルダのライブラリLovyanGFXバージョン0.2.1を使用中:/Users/*****/Documents/Arduino/libraries/LovyanGFX
exit status 1
invalid conversion from 'void
(
)(char
, int32_t
) {aka void
(
)(char*, int*)}' to 'void* ()(const char, int32_t*) {aka void* ()(const char, int*)}' [-fpermissive]

GifDecoder Library now uses AnimatedGIF

Hi Larry,

After getting a handle on the AnimatedGIF API and the needs of myself and people that use my projects, I decided to turn GifDecoder into mostly a wrapper class that simplifies the interface to AnimatedGIF. It hides the complicated - at least for a beginner - disposal code inside the class, provides a familiar API for people that used the AnimatedGIFs sketch or GifDecoder in the past, and adds some automatic tracking of stats that AnimatedGIF currently doesn't do unless you parse the whole GIF separate from displaying it. I made sure to credit you and point people to your library for donations and sponsorships in the README.

https://github.com/pixelmatix/GifDecoder

STM32F401

Hello

You can make it work for STM32F401 and ST7789_NOCS

regards

Add support for double buffer mode

Hi @bitbank2 ,

wondering if current library could be combine with led matrix double buffer mode?
since double buffer will write the detail on the buffer before swapping the buffer on the screen. from the matrix example, the draw function write pixel by line which i not able to slice the mapping from buffer to the screen. also i not able to control the play and the stop of animation as the playframe are blocking the codes and require to close the running process.

not sure how to make this to work. basically, how to get control on the gif display. should i run the playframe in separate process?

thanks in advanced.

Can I use this for just decoding GIF images?

I'm currently using stbi__load_gif_main to load GIF files, but it is too slow for me (because of dynamic allocation). This library seemed interesting, as it states that it doesn't use dynamic allocation, but I want to use it for desktop applications, not Arduino.

If this library isn't applicable to my use case, could you recommend me a fast GIF decoder library for C++, or is dynamic allocation inevitable?

Thanks!

GIFs created with gifencoder.js have trailing zeros, and won't parse correctly

The issue I'm having is specifically with exports from noisedeck.app, which I found uses this for encoding GIFs under the hood: https://github.com/jnordberg/gif.js

Three example GIFs:

noisedeck-1624031604921

You can only change the GIF dimensions and number of frames in the pro version
nd-chlorine-64x64

Yes this is ugly but maybe it's useful to have a GIF with just two frames for brevity
noisedeck-1624029961748

There's trailing zeros left after the end of the GIF:

image

I'm guessing this is a result of the encoder using pages for storage and not trimming the excess at the end.

https://github.com/jnordberg/gif.js/blob/master/src/GIFEncoder.js#L19

GIFParseInfo() fails here:

https://github.com/bitbank2/AnimatedGIF/blob/master/src/gif.inl#L401

Here's my quick fix. There's probably a better place to look for the trailer byte, but this works:

        else // invalid byte, stop decoding
        {
            if (pPage->GIFFile.iSize - iStartPos < 32) {
                // non-image bytes at end of file?
                pPage->iError = GIF_EMPTY_FRAME;

            } else if(p[iOffset] == ';') {
                // Trailer Byte - End of the GIF Data Stream
                pPage->iError = GIF_EMPTY_FRAME;
            }
            else {
                /* Bad header info */
                pPage->iError = GIF_DECODE_ERROR;
                Serial.print("byte: ");
                Serial.print(p[iOffset], HEX);
                Serial.print(" pos: ");
                Serial.print(iOffset, HEX);
            }
            return 0;
        }

Strange extremely long seek time with SPIFFS on ESP32C3 at the first frame

While playing the same GIF in infinite loop from SPIFFS the first frame of each loop is significantly slowed down to the point the loop is not smooth at all, i know this is not the gif itself because when played on windows it loops smoothly. At first i assumed it was due to the file reloading at each loop, but after digging i found this is caused solely by the seek call. I dont understand why but the first seek is extremely slow, in the order or 20ms, and subsequent seeks are very short, in the order of 50 to 800us.
Do you know what could cause this and how to go around this issue? The loop begins on the red background.

image

e1

ESP8266 SPIFFS callbacks

Hello Larry,
thank you for your awesome work. I tried your AnimatedGIF library with ESP32 and ESP8266. With ESP32 and ST7765 display, it works like a charm. Unfortunately for ESP8266 I stuggle with implementation of callbacks for SPIFFS.

#include <FS.h>
#include <AnimatedGIF.h>

AnimatedGIF gif;
File f;

void * GIFOpenFile(const char *fname, int32_t *pSize)
{
  f = SPIFFS.open(fname);
  if (f)
  {
    *pSize = f.size();
    return (void *)&f;
  }
  return NULL;
} 

bool displayGIF(char* fname) {
   if (gif.open(fname, GIFOpenFile, GIFCloseFile, GIFReadFile, GIFSeekFile, GIFDraw)) {
      GIFINFO gi;
      Serial.printf("Successfully opened GIF; Canvas size = %d x %d\n", gif.getCanvasWidth(), gif.getCanvasHeight());
      while (gif.playFrame(true, NULL))   {  }
      gif.close();
      return true;
    } else {
      Serial.printf("Error opening file = %d\n", gif.getLastError());
      return false;
    }
}

void setup() {
//SPIFFS and TFT init stuff here

 displayGIF("/test.gif");
}

Line f = SPIFFS.open(fname); throws following error during compilation:

D:\Projekty\esp8266-weather-station-color\esp8266-weather-station-color-rad\esp8266-weather-station-color-rad.ino: In function 'void* GIFOpenFile(const char*, int32_t*)':
esp8266-weather-station-color-rad:185:24: error: no matching function for call to 'fs::FS::open(const char*&)'
185 | f = SPIFFS.open(fname);
| ^
In file included from sketch\GfxUi.h:22,
from D:\Projekty\esp8266-weather-station-color\esp8266-weather-station-color-rad\esp8266-weather-station-color-rad.ino:25:
C:\Users\Petr Hanu�\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\3.0.1\cores\esp8266/FS.h:215:10: note: candidate: 'fs::File fs::FS::open(const char*, const char*)'
215 | File open(const char* path, const char* mode);
| ^~~~
C:\Users\Petr Hanu�\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\3.0.1\cores\esp8266/FS.h:215:10: note: candidate expects 2 arguments, 1 provided
C:\Users\Petr Hanu�\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\3.0.1\cores\esp8266/FS.h:216:10: note: candidate: 'fs::File fs::FS::open(const String&, const char*)'
216 | File open(const String& path, const char* mode);
| ^~~~
C:\Users\Petr Hanu�\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\3.0.1\cores\esp8266/FS.h:216:10: note: candidate expects 2 arguments, 1 provided
D:\Projekty\esp8266-weather-station-color\esp8266-weather-station-color-rad\esp8266-weather-station-color-rad.ino: In function 'void GIFDraw(GIFDRAW*)':

I'm using Arduino Boards v 2.7.4 or 3.0.1.

I would like to kindly ask you for advice with the callbacks for ESP8266.
Thank you,
Cheers, phanus

Issue using AnimatedGIFPanel

Hi guys, im using AnimatedGIFPanel.ino example with an esp32s3 devkit C and a led matrix 64x32 P3. I was able to display the 64x32 gifs after loading them on flash with spiffs but since yesterday I always get blank matrix. I think I've updated some libraries, ive tryed to rollback but nothing changed. Im able to run correctly the ESP32_LEDMatrix_I2S tho. Do you have any ideas? Thanks

Error loading a gif from an SD card

I get this error when trying to load a gif from an sdcard

Guru Meditation Error: Core 1 panic'ed (LoadProhibited). Exception was unhandled.

I am using the example ESP32-LGFX-SDCard-GifPlayer

#include "Arduino.h"
#include <TFT_eSPI.h>
TFT_eSPI tft = TFT_eSPI();
#define DISPLAY_WIDTH tft.width()
#include <SD.h>
#include <vector> 
#include "AnimatedGIF.h"

                        // Note: Do not use SPI DMA if reading GIF images from SPI SD card on same bus as TFT
#define NORMAL_SPEED  // Comment out for rame rate for render speed test

AnimatedGIF gif;

// rule: loop GIF at least during 3s, maximum 5 times, and don't loop/animate longer than 30s per GIF
const int maxLoopIterations =     50; // stop after this amount of loops
const int maxLoopsDuration  =  30000; // ms, max cumulated time after the GIF will break loop
const int maxGifDuration    = 300000; // ms, max GIF duration

// used to center image based on GIF dimensions
static int xOffset = 0;
static int yOffset = 0;

static int totalFiles = 0; // GIF files count
static int currentFile = 0;
static int lastFile = -1;

char GifComment[256];

static File FSGifFile; // temp gif file holder
static File GifRootFolder; // directory listing

std::vector<std::string> GifFiles; // GIF files path


static void MyCustomDelay( unsigned long ms ) {
  delay( ms );
  //log_d("delay %d\n", ms);
}


static void * GIFOpenFile(const char *fname, int32_t *pSize)
{
  //log_d("GIFOpenFile( %s )\n", fname );
  FSGifFile = SD.open(fname);
  if (FSGifFile) {
    *pSize = FSGifFile.size();
    return (void *)&FSGifFile;
  }
  return NULL;
}


static void GIFCloseFile(void *pHandle)
{
  File *f = static_cast<File *>(pHandle);
  if (f != NULL){
     f->close();
     log_n("Close file 1!");
  }
   log_n("Close file 2!");
}


static int32_t GIFReadFile(GIFFILE *pFile, uint8_t *pBuf, int32_t iLen)
{ log_n("GIFReadFile 1!");
  int32_t iBytesRead;
  
  iBytesRead = iLen;
  File *f = static_cast<File *>(pFile->fHandle);
  // Note: If you read a file all the way to the last byte, seek() stops working
  if ((pFile->iSize - pFile->iPos) < iLen)
      iBytesRead = pFile->iSize - pFile->iPos - 1; // <-- ugly work-around
  if (iBytesRead <= 0)
      return 0;
  iBytesRead = (int32_t)f->read(pBuf, iBytesRead);
  pFile->iPos = f->position();
  log_n("GIFReadFile 2!");
  return iBytesRead;
}


static int32_t GIFSeekFile(GIFFILE *pFile, int32_t iPosition)
{log_n("GIFSeekFile 1!");
  int i = micros();
  File *f = static_cast<File *>(pFile->fHandle);
  f->seek(iPosition);
  pFile->iPos = (int32_t)f->position();
  i = micros() - i;
  //log_d("Seek time = %d us\n", i);
  log_n("GIFSeekFile 2!");
  return pFile->iPos;
}


static void TFTDraw(int x, int y, int w, int h, uint16_t* lBuf )
{
  tft.pushRect( x+xOffset, y+yOffset, w, h, lBuf );
}


// Draw a line of image directly on the LCD
void GIFDraw(GIFDRAW *pDraw)
{log_n("GIFDraw 1!");
  uint8_t *s;
  uint16_t *d, *usPalette, usTemp[320];
  int x, y, iWidth;

  iWidth = pDraw->iWidth;
  if (iWidth > DISPLAY_WIDTH)
      iWidth = DISPLAY_WIDTH;
  usPalette = pDraw->pPalette;
  y = pDraw->iY + pDraw->y; // current line

  s = pDraw->pPixels;
  if (pDraw->ucDisposalMethod == 2) {// restore to background color
    for (x=0; x<iWidth; x++) {
      if (s[x] == pDraw->ucTransparent)
          s[x] = pDraw->ucBackground;
    }
    pDraw->ucHasTransparency = 0;
  }
  // Apply the new pixels to the main image
  if (pDraw->ucHasTransparency) { // if transparency used
    uint8_t *pEnd, c, ucTransparent = pDraw->ucTransparent;
    int x, iCount;
    pEnd = s + iWidth;
    x = 0;
    iCount = 0; // count non-transparent pixels
    while(x < iWidth) {
      c = ucTransparent-1;
      d = usTemp;
      while (c != ucTransparent && s < pEnd) {
        c = *s++;
        if (c == ucTransparent) { // done, stop
          s--; // back up to treat it like transparent
        } else { // opaque
            *d++ = usPalette[c];
            iCount++;
        }
      } // while looking for opaque pixels
      if (iCount) { // any opaque pixels?
        TFTDraw( pDraw->iX+x, y, iCount, 1, (uint16_t*)usTemp );
        x += iCount;
        iCount = 0;
      }
      // no, look for a run of transparent pixels
      c = ucTransparent;
      while (c == ucTransparent && s < pEnd) {
        c = *s++;
        if (c == ucTransparent)
            iCount++;
        else
            s--;
      }
      if (iCount) {
        x += iCount; // skip these
        iCount = 0;
      }
    }
  } else {
    s = pDraw->pPixels;
    // Translate the 8-bit pixels through the RGB565 palette (already byte reversed)
    for (x=0; x<iWidth; x++)
      usTemp[x] = usPalette[*s++];
    TFTDraw( pDraw->iX, y, iWidth, 1, (uint16_t*)usTemp );
  }
} /* GIFDraw() */


int gifPlay( char* gifPath )
{ // 0=infinite
log_n("Play gif!");
  gif.begin(BIG_ENDIAN_PIXELS);

  if( ! gif.open( gifPath, GIFOpenFile, GIFCloseFile, GIFReadFile, GIFSeekFile, GIFDraw ) ) {
    log_n("Could not open gif %s", gifPath );
    return maxLoopsDuration;
  }

  int frameDelay = 0; // store delay for the last frame
  int then = 0; // store overall delay
  bool showcomment = false;

  // center the GIF !!
  int w = gif.getCanvasWidth();
  int h = gif.getCanvasHeight();
  xOffset = ( tft.width()  - w )  /2;
  yOffset = ( tft.height() - h ) /2;

  if( lastFile != currentFile ) {
    log_n("Playing %s [%d,%d] with offset [%d,%d]", gifPath, w, h, xOffset, yOffset );
    lastFile = currentFile;
    showcomment = true;
  }

  while (gif.playFrame(true, &frameDelay)) {
    if( showcomment )
      if (gif.getComment(GifComment))
        log_n("GIF Comment: %s", GifComment);

    then += frameDelay;
    if( then > maxGifDuration ) { // avoid being trapped in infinite GIF's
      //log_w("Broke the GIF loop, max duration exceeded");
      break;
    }
  }

  gif.close();

  return then;
}


int getGifInventory( const char* basePath )
{
  int amount = 0;
  GifRootFolder = SD.open(basePath);
  if(!GifRootFolder){
    log_n("Failed to open directory");
    return 0;
  }

  if(!GifRootFolder.isDirectory()){
    log_n("Not a directory");
    return 0;
  }

  File file = GifRootFolder.openNextFile();

  tft.setTextColor( TFT_WHITE, TFT_BLACK );
  tft.setTextSize( 2 );

  int textPosX = tft.width()/2 - 16;
  int textPosY = tft.height()/2 - 10;

  tft.drawString("GIF Files:", textPosX-40, textPosY-20 );

  while( file ) {
    if(!file.isDirectory()) {
      GifFiles.push_back( file.name() );
      amount++;
      tft.drawString(String(amount), textPosX, textPosY );
      file.close();
    }
    file = GifRootFolder.openNextFile();
  }
  GifRootFolder.close();
  log_n("Found %d GIF files", amount);
  return amount;
}




void setup()
{
  int attempts = 0;
  int maxAttempts = 50;
  int delayBetweenAttempts = 300;
  bool isblinked = false;
  while(! SD.begin() ) {
    log_n("SD Card mount failed! (attempt %d of %d)", attempts, maxAttempts );
    isblinked = !isblinked;
    attempts++;
    if( isblinked ) {
      tft.setTextColor( TFT_WHITE, TFT_BLACK );
    } else {
      tft.setTextColor( TFT_BLACK, TFT_WHITE );
    }
    tft.drawString( "INSERT SD", tft.width()/2, tft.height()/2 );

    
    delay( delayBetweenAttempts );
    SD.begin();
  }

  log_n("SD Card mounted!");

  tft.begin();
  tft.fillScreen(TFT_BLACK);

  totalFiles = getGifInventory( "/gif" ); // scan the SD card GIF folder

}



void loop()
{


  const char * fileName = GifFiles[currentFile++%totalFiles].c_str();

  int loops = maxLoopIterations; // max loops
  int durationControl = maxLoopsDuration; // force break loop after xxx ms

  while(loops-->0 && durationControl > 0 ) {
    durationControl -= gifPlay( (char*)fileName );
    gif.reset();
  }

}

[IDEA] - Trigger the advance of the gif with an external event

Hello bitbank2,

I am continuing my experiments with your library.
I noticed small position defects with "optimized" transparent gifs.
But everything is fine if I remove the optimization with software like Gif Movie Gear

Now I would like to synchronize or trigger the frames of the gif, one by one, with external events.

Some kind of function like this:
playFrameNumber (int frameNB) {…}
or frameNB would be the number of my choice, which I could increment in a loop with other graphics.

This function would allow for example:

  • Display a Gif image with a clock, and change frames every second.
  • Or a Gif thermometer which changes according to the values received.
  • Or move an object or a text followed by a character (gif) walking at the same speed.

So far I have just tried it with your normal playFrame function.
I canceled the delay (with bSync = false) and tried to pass two more values:
int AnimatedGIF :: playFrame (bool bSync, int * delayMilliseconds, int curFrameNb, int frameToPlay) {…}

This way I can scroll through all the frames quickly and get out of the loop to block the requested frame.
But fast frames are also displayed for a fraction of a second, and that causes artefacts.

The ideal would be to advance the file to the correct position without displaying the previous frames, or displaying them in black (color 0).

If you have an idea that is more "elegant" than mine, I am listening.
Thank you for your work.

Questions concerning rendering from SPIFFS.

Hi,

I successfully rendered a gif (converted to .h file) from memory on my TTGO T-Dispaly (ESP32 with 16MB flash and a TFT_eSPI display).
I wanted to try render the same gif from flash memory. I successfully saved the .gif in my filesystem and can open the file, however I don't see anything appearing on the screen.+
Can I use the same GIFDRAW callback as the TFT_eSPI_memory example, while using the GIFOpenFile, GIFCloseFile, GIFReadFile, GIFSeekFile functions from the SD examples? (with GIF open modified to use SPIFFS).

Btw the gif I'm trying to use is this one:

beer_gif3

Add Error Codes for Debugging?

I just tried playing a handful of GIFs that work just fine with the GifDecoder library but only one of them opened with AnimatedGIF's play_all_sd_files sketch. I'd like to debug but the library doesn't make it easy to get debug information out. I'd like to add in return error codes for debugging as having people paste GIFs into GitHub Issues and ask "why doesn't this work?" doesn't scale very well.

e.g. https://github.com/pixelmatix/GifDecoder/blob/master/src/GifDecoder_Impl.h#L63

You've already defined that GifInit() returns 0 for an error, and I don't want to break existing sketches. I'm thinking about adding error codes to GIFParseInfo(), and a new GifInitDebug() function that passes on the return codes from GIFParseInfo(). Does that sound good, or do you have a better way to do it?

linux/main.c: too few arguments to function ‘GIF_begin’

The linux demo linux/main.c fails to build:

main.c:24:5: error: too few arguments to function ‘GIF_begin’
     GIF_begin(&gif, BIG_ENDIAN_PIXELS);
     ^~~~~~~~~
In file included from main.c:8:0:
../src/AnimatedGIF.h:185:10: note: declared here
     void GIF_begin(GIFIMAGE *pGIF, int iEndian , unsigned char ucPaletteType);
          ^~~~~~~~~

Patch attached adds GIF_PALETTE_RGB888 for the missing third arg, allowing the linux demo to build.

Additional note: {BIG|LITTLE}_ENDIAN_PIXELS is actually only relevant for GIF_PALETTE_RGB565, so perhaps a better scheme would be to switch (back) to a two-arg form for GIF_begin() with three constants named e.g.:

	GIF_PALETTE_RGB565_BE
	GIF_PALETTE_RGB565_LE
	GIF_PALETTE_RGB888

A little bug in timing

In playFrame()
When call with sync=true, it is ok, but when call with not sync and receive back the delay, you do not do the calculation of time being taken as in the other case.

Actually:

if (bSync)
{
    lTime = millis() - lTime;
    if (lTime < _gif.iFrameDelay) // need to pause a bit
        delay(_gif.iFrameDelay - lTime);
}
if (delayMilliseconds) // if not NULL, return the frame delay time
        *delayMilliseconds = _gif.iFrameDelay;

It think it must be:

lTime = millis() - lTime;
if (bSync)
{
    if (lTime < _gif.iFrameDelay) // need to pause a bit
        delay(_gif.iFrameDelay - lTime);
}
if (delayMilliseconds) // if not NULL, return the frame delay time
{
    if (lTime < _gif.iFrameDelay) // need to pause a bit
        *delayMilliseconds = _gif.iFrameDelay - lTime;
    else
        *delayMilliseconds = _gif.iFrameDelay;
}

Hangs on gif.playFrame(true, NULL)

I've been trying to figure this out for a bit now, but after everything gets set up and I try to display the first frame, it hangs, not even giving an error code. I'm working on a PyPortal Titano, with everything up to that point working perfectly. Here's the code in the main loop:

Serial.println("Starting Loop!");
    // read diagnostics (optional but can help debug problems)
    uint8_t x = tft.readcommand8(HX8357_RDPOWMODE);
    Serial.print("Display Power Mode: 0x"); Serial.println(x, HEX);
    x = tft.readcommand8(HX8357_RDMADCTL);
    Serial.print("MADCTL Mode: 0x"); Serial.println(x, HEX);
    x = tft.readcommand8(HX8357_RDCOLMOD);
    Serial.print("Pixel Format: 0x"); Serial.println(x, HEX);
    x = tft.readcommand8(HX8357_RDDIM);
    Serial.print("Image Format: 0x"); Serial.println(x, HEX);
    x = tft.readcommand8(HX8357_RDDSDR);
    Serial.print("Self Diagnostic: 0x"); Serial.println(x, HEX); 

	Serial.println("About to call gif.open");
	if (gif.open("/lofi.gif", GIFOpenFile, GIFCloseFile, GIFReadFile, GIFSeekFile, GIFDraw))
	{
		GIFINFO gi;
		Serial.printf("Successfully opened GIF; Canvas size = %d x %d\n", gif.getCanvasWidth(), gif.getCanvasHeight());
		if (gif.getInfo(&gi)) {
		Serial.printf("frame count: %d\n", gi.iFrameCount);
		Serial.printf("duration: %d ms\n", gi.iDuration);
		Serial.printf("max delay: %d ms\n", gi.iMaxDelay);
		Serial.printf("min delay: %d ms\n", gi.iMinDelay);
		Serial.println("4");
		}


		while (true)
		{
			Serial.println("5");
			Serial.printf("code: %d \n", gif.playFrame(true, NULL));
			delay(100);
		}
		gif.close();
	}
	else
	{
		Serial.printf("Error opening file = %d\n", gif.getLastError());
		while (1)
		{};
	}

and the output:

Starting Loop!
Display Power Mode: 0x0
MADCTL Mode: 0x9C      
Pixel Format: 0x60
Image Format: 0x5
Self Diagnostic: 0x0
About to call gif.open
Successfully opened GIF; Canvas size = 480 x 320
frame count: 188
duration: 18800 ms
max delay: 100 ms
min delay: 100 ms
4
5

The 4 and the 5 are just there for simple debugging, to determine that it has reached that point. I've modified the MAX_WIDTH in AnimatedGif.h to equal 480, and the gif in question is 480x320. I've attached the file in question below:
lofi

Any idea why it would hang? Again, there's no result from the function, just infinite delay.

Image edges are jagged on esp8266

Hi Larry, i was able to play gifs stored with littlefs in nor flash.
But i found most gif images edges are jagged, especially image edges with small lines.
https://youtu.be/CwPEhcc7Wx0

I've tried diffrent TFT_eSPI settings, but can't help.
I wonder if it's limits on esp8266, or i need do some settings with AnimatedGIF library, or something limited by the sreen?

I've tested jpg and bmp images display with no such obvious problem.

Board: esp8266
Screen: st7789 spi screen
Tested gif soure image:
sun2

Thanks Larry.

Some problem GIFs

This is looking super encouraging! Wow. With some tweaks, on PyPortal it’s able to play back a full-screen 200 MB GIF (Big Buck Bunny) at full speed (I’ll put the GIF on DropBox if you’re interested). Adafruit_Arcada_GifDecoder can’t quite keep up with that one. Looks like about a 10% boost.

A few GIFs are having trouble. They’re in this branch, which includes the PyPortal example:
https://github.com/PaintYourDragon/AnimatedGIF/tree/pb-experiments
In the 'gifs' folder:

  • beast.gif has issues with transparency and frame timing (view in web browser to compare). It’s close but looks like something’s off by one. Adafruit_Arcada_GifDecoder gets the transparency right but still has timing trouble with that one.
  • dragons.gif doesn’t display anything. It’s doing the frame loop, but displaying nothing and eventually hangs (not sure if it’s the last frame/EOF or in the middle somewhere).
  • krampus-anim.gif has issues similar to dragons.gif. Adafruit_Arcada_GifDecoder sorta plays this one but has trouble with the transparency (again, compare against browser).

Also in that fork/branch, small change in AnimatedGIF.h sets MAX_WIDTH to 480 on PyPortal Titano, 320 otherwise. PyPortal example uses SdFat (instead of SD library), gets a noticeable boost from that, and also does some big-endian DMA stuff to the screen.

rgb24 Palette support

Hi Larry, I've been maintaining Craig Lindley's AnimatedGIFs sketch for years, and had always wanted to turn it into a platform-independent GIF Decoder library. I finally got a chance to start this year, a few weeks before you released your library and made my work obsolete :-)

I'd like to start using your library for my GIF projects, but it's missing one critical feature: rgb24 palette support. I'm using GIFs to drive LED matrix displays with high color depth, for projects like this, so I need to get the full 24-bits of color from the source image:

https://www.instructables.com/Continuum-Slow-Motion-LED-Art-Display/

It looks trivial enough to fork your library and change to using rgb24 palettes, but I'd prefer to have the palette type be selectable in the sketch (not through changing a #define in the library), without slowing existing rgb565 performance, so other people can use the feature easily. The best option I can come up with right now is to use C++ templates to choose between the two palette types, but I saw you rewrote your library to use C, so that's probably out. Arduino doesn't make this kind of library configuration easy.

Do you have any easy solution for this off the top of your head?

Would you even want to support rgb24 in addition to rgb565 in your library, or is this something I should tackle on my own?

I've been enjoying your updates on twitter with your different display libraries. I don't have any active E-Ink projects, but I do have some in mind that could make use of the work you're doing, keep it up!

Handling of GIF_EMPTY_FRAME error is confusing for user

If someone calls AnimatedGIF::playFrame(false, &delayMilliseconds), and gets a return code of 0 (good result and no more frames exist), they have to assume that a frame was processed and that they should delay for delayMilliseconds. On GIFs that have an empty frame at the end, that results in the last frame being displayed for an extra unknown duration (could be negative!), as there was no frame processed and delayMilliseconds is unchanged. (In my case delayMilliseconds is holding the same value as the previous call to playFrame() so the delay is twice as long as it should be).

https://github.com/bitbank2/AnimatedGIF/blob/master/src/AnimatedGIF.cpp#L196

I'm fixing this in the GifDecoder wrapper by loading a known bad value (-1) into delayMilliseconds and checking for that value if I get a return code of 0, indicating that there was a GIF_EMPTY_FRAME error, as I don't see any other way of determining that without modifying AnimatedGIF.

Suggested fix:

  • Set delayMilliseconds to zero when seeing the GIF_EMPTY_FRAME error, so if someone uses delayMilliseconds they're not delaying for an unknown amount of time.
    • That fix is no good for me on it's own, as with GifDecoder I call an updateScreenCallback that should only be called if there's new data in the frame, and I also keep track of the number of frames in the GIF (incrementing after each successful call to playFrame())
  • Also keep the GIF_EMPTY_FRAME error, and don't replace with GIF_SUCCESS.
    • Most users won't be looking for the last error on playFrame() return of 0, and if they are, they can look for both GIF_EMPTY_FRAME and GIF_SUCCESS.
    • I can use the combination of the return code and getLastError() to determine if there was a frame processed

Huge ram usage

Hi Larry,

thanks for this great library and sorry for spamming your repo.
after i enabled the AnimatedGif it consume around 23,988 bytes of RAM which quite huge.

i only require to render 192x64 gif image. is there option/params/config in the library that i can tweak or change to minimize the RAM usage as per my use / needed? do this based on the screen width and height calculation?

edit: i have read this: reduce memory usage on ESP32

AnimatedGIF *pGIF = static_cast<AnimatedGIF *>(my_memory_pool);

i not sure if i can use this approach as other stuff/library (wifi, tls, etc2..) memory are controlled by the controller.
can i malloc those memory pool and free later? can shed light on how to use malloc? i just started with c.

this issue is happen after importing the AnimatedGIF.h and no begin was call yet. by only importing the library and not running the gif read, it already consume 23Kb of ram. i have tried the malloc and free as example but seem the RAM usage is still the same.

thanks,
ts

In ESP32_LEDMatrix_I2S.ino iWidth is not intialized.

Amazing Project, Thanks for sharing. Found a tiny bug.

In ESP32_LEDMatrix_I2S.ino iWidth is not initialized.

int x, y, iWidth;

usPalette = pDraw->pPalette;
y = pDraw->iY + pDraw->y; // current line

s = pDraw->pPixels;
if (pDraw->ucDisposalMethod == 2) // restore to background color
{
for (x = 0; x < iWidth; x++)

Can be fixed by adding on line 69
iWidth = pDraw->iWidth;

Thanx

gif larger than 320 crash

Hello,
i am trying to play gif larger than 320 on ESP32.
I modified MAX_WIDTH in .h file, but it crashs.
It seems to come from GIFMakePels.
Any idea what could fail ?

stack trace doesn't git much informations ...

09:44:47.632 > Guru Meditation Error: Core  1 panic'ed (Double exception).
09:44:47.633 >
09:44:47.633 > Core  1 register dump:
09:44:47.633 > PC      : 0x40092223  PS      : 0x00040636  A0      : 0x800e5b22  A1      : 0x3ffecf70
09:44:47.634 > A2      : 0x3ffed220  A3      : 0x3fff26a6  A4      : 0x000000ff  A5      : 0x00000000
09:44:47.635 > A6      : 0x3fff26a6  A7      : 0x3fff36b0  A8      : 0x0000015e  A9      : 0x00000000
09:44:47.636 > A10     : 0x00000000  A11     : 0x00000000  A12     : 0x0000015e  A13     : 0x00000001
09:44:47.637 > A14     : 0x3ffecf70  A15     : 0xff000000  SAR     : 0x00000015  EXCCAUSE: 0x00000002  
09:44:47.638 > EXCVADDR: 0xfffffff1  LBEG    : 0x400014fd  LEND    : 0x4000150d  LCOUNT  : 0xffffffff
09:44:47.639 >
09:44:47.639 >
09:44:47.639 > Backtrace:0x40092220:0x3ffecf700x400e5b1f:0x3ffed220 0x400e5b1f:0x00000000  |<-CORRUPTED
09:44:47.687 >   #0  0x40092220:0x3ffecf700 in _xt_context_save at C:\Users\Francois\.platformio\packages\framework-espidf\components\freertos\port\xtensa/xtensa_context.S:194
09:44:47.687 >
09:44:47.687 >
09:44:47.687 >
09:44:47.687 >

Loading Gif into memory from Web

Hi @bitbank2 thanks for providing us with an awesome library! Do you have any suggestions/examples on how I can pull down a GIF hosted from a server into memory and then pass that into the library to open() it?

Play SD card gif with tft screen on esp8266

Hello Larry, sorry to bother you again, but I have tried all the methods I thought of and there is no better idea. Have to find an expert like you to give some clues. I tried to share SPI with TFT screen and SD card on ESP8266 platform, after debugging for a lot of time, finally it worked, but the effect is not as expected. My original purpose was to transcode the video to gif format through ffmpeg on the Linux platform, and then stored on the SD card of the ESP8266 for smooth playback. The gif image used in the test was about 50MB in size. The problem I am facing now one is that it freezes every frame when playing, and the other is that the program crashes after playing for a while. I am wondering whether this is an impossible goal on ESP8266. If you have free time, and at the same time if it is convenient for you, I can send you the code and image. I want to figure out why it can't be achieved, maybe finally have to switch to ESP32 after the last try.

The following video is the problem I encountered:
https://www.youtube.com/watch?v=pcBab8AUj-Q

Thank you again for your time.

Decode whole frame to RAM

I try to use your library with Adafruit matrix (WS2812B). I found that writing each line separately and call matrix.show every time is very slow.
So, better practice will be decode full frame to RAM, but I can't implement it using your code, because callback function GIFDraw uses line instead of frame. How to modify that?

gif.h

Hi
How did you create the gif.h
regards

Example TFT_eSPI_memory crash on ESP8266

Hi, nice work first !

I think it crashed here in git.playFrame(true,NULL);

Changes i made:
gif.begin(LITTLE_ENDIAN_PIXELS);// little endian for esp8266, board esp-12e
void GIFDraw(GIFDRAW *pDraw) copyed from GIFDRAW.ino
#define GIF_IMAGE ucBadgers // No DMA 63 fps, DMA: 71fps // tested this gif image

TFT Screen is st7789 240x240 size.

Logs printed on serial console:
Successfully opened GIF; Canvas size = 160 x 120
tft size = 240 x 240

--------------- CUT HERE FOR EXCEPTION DECODER ---------------
...

Any help will be appreciated, thanks.

Compilation error Example: play_all_sd_files.ino

Hello,
I used the Explora display with st7735r with integrated sd-card reader.
The core is ESP32 and the big_mem_demo example works fine. But when I insert SD-card this schetch has random pixels on the edges of the image even though the gif is spinning.
I kept HIGH sd_cs with a pull-up but to no avail.

Instead the example file play_all_sd_files.ino gives errors during the compilation due to problems of type concerning the callbacks.
I fixed the problems and now the file is compiled but the gif files on the sd-card are not displayed and they are opened and read correctly at least so it reports the log from serial.

#include <AnimatedGIF.h>
#include <bb_spi_lcd.h>
#include <SPI.h>
#include <SD.h>

// Demo sketch to play all GIF files in a directory
// Tested on TTGO T-Camera Plus

#define LED_PIN   16
#define DC_PIN    22
#define RES_PIN   21
#define CS_PIN    5
#define SCK_PIN   18
#define MISO_PIN  19
#define MOSI_PIN  23
#define SD_CS_PIN 17

#define DISPLAY_WIDTH 128
#define DISPLAY_HEIGHT 160

SPILCD lcd;

AnimatedGIF gif;
File f;
int x_offset, y_offset;

// Draw a line of image directly on the LCD
void GIFDraw(GIFDRAW *pDraw)
{
    uint8_t *s;
    uint16_t *d, *usPalette, usTemp[320];
    int x, y, iWidth;

    iWidth = pDraw->iWidth;
    if (iWidth > DISPLAY_WIDTH)
       iWidth = DISPLAY_WIDTH;
    usPalette = pDraw->pPalette;
    y = pDraw->iY + pDraw->y; // current line
    
    s = pDraw->pPixels;
    if (pDraw->ucDisposalMethod == 2) // restore to background color
    {
      for (x=0; x<iWidth; x++)
      {
        if (s[x] == pDraw->ucTransparent)
           s[x] = pDraw->ucBackground;
      }
      pDraw->ucHasTransparency = 0;
    }
    // Apply the new pixels to the main image
    if (pDraw->ucHasTransparency) // if transparency used
    {
      uint8_t *pEnd, c, ucTransparent = pDraw->ucTransparent;
      int x, iCount;
      pEnd = s + iWidth;
      x = 0;
      iCount = 0; // count non-transparent pixels
      while(x < iWidth)
      {
        c = ucTransparent-1;
        d = usTemp;
        while (c != ucTransparent && s < pEnd)
        {
          c = *s++;
          if (c == ucTransparent) // done, stop
          {
            s--; // back up to treat it like transparent
          }
          else // opaque
          {
             *d++ = usPalette[c];
             iCount++;
          }
        } // while looking for opaque pixels
        if (iCount) // any opaque pixels?
        {
          spilcdSetPosition(&lcd, pDraw->iX+x+x_offset, y+y_offset, iCount, 1, 1);
          spilcdWriteDataBlock(&lcd, (uint8_t *)usTemp, iCount*2, 1);
          x += iCount;
          iCount = 0;
        }
        // no, look for a run of transparent pixels
        c = ucTransparent;
        while (c == ucTransparent && s < pEnd)
        {
          c = *s++;
          if (c == ucTransparent)
             iCount++;
          else
             s--; 
        }
        if (iCount)
        {
          x += iCount; // skip these
          iCount = 0;
        }
      }
    }
    else
    {
      s = pDraw->pPixels;
      // Translate the 8-bit pixels through the RGB565 palette (already byte reversed)
      for (x=0; x<iWidth; x++)
        usTemp[x] = usPalette[*s++];
      spilcdSetPosition(&lcd, pDraw->iX+x_offset, y+y_offset, iWidth, 1, 1);
      spilcdWriteDataBlock(&lcd, (uint8_t *)usTemp, iWidth*2, 1);
    }
} /* GIFDraw() */

void * GIFOpenFile(const char *fname, int32_t *pSize)
{
  f = SD.open(fname);
  if (f)
  {
    *pSize = f.size();
    return (void *)&f;
  }
  return NULL;
} /* GIFOpenFile() */

void GIFCloseFile(void *pHandle)
{
  File *f = static_cast<File *>(pHandle);
  if (f != NULL)
     f->close();
} /* GIFCloseFile() */

int32_t GIFReadFile(GIFFILE *pFile, uint8_t *pBuf, int32_t iLen)
{
    int32_t iBytesRead;
    iBytesRead = iLen;
    File *f = static_cast<File *>(pFile->fHandle);
    // Note: If you read a file all the way to the last byte, seek() stops working
    if ((pFile->iSize - pFile->iPos) < iLen)
       iBytesRead = pFile->iSize - pFile->iPos - 1; // <-- ugly work-around
    if (iBytesRead <= 0)
       return 0;
    iBytesRead = (int32_t)f->read(pBuf, iBytesRead);
    pFile->iPos = f->position();
    return iBytesRead;
} /* GIFReadFile() */

int32_t GIFSeekFile(GIFFILE *pFile, int32_t iPosition)
{ 
  int i = micros();
  File *f = static_cast<File *>(pFile->fHandle);
  f->seek(iPosition);
  pFile->iPos = (int32_t)f->position();
  i = micros() - i;
//  Serial.printf("Seek time = %d us\n", i);
  return pFile->iPos;
} /* GIFSeekFile() */

void setup() {
  Serial.begin(115200);
  while (!Serial);

// Need to initialize SPI before calling SD.begin()
  SPI.begin(SCK_PIN, MISO_PIN, MOSI_PIN, SD_CS_PIN);
  if (!SD.begin(SD_CS_PIN)) {
    Serial.println("SD card init failed!");
    while (1); // SD initialisation failed so wait here
  }
  Serial.println("SD Card init success!");

  spilcdInit(&lcd, LCD_ST7735R, FLAGS_NONE, 32000000, CS_PIN, DC_PIN, RES_PIN, LED_PIN, MISO_PIN, MOSI_PIN, SCK_PIN);
  gif.begin(BIG_ENDIAN_PIXELS);
}

void ShowGIF(const char *name)
{
  spilcdFill(&lcd,1,1);
  
  if (gif.open(name, GIFOpenFile, GIFCloseFile, GIFReadFile, GIFSeekFile, GIFDraw))
  {
    x_offset = (DISPLAY_WIDTH - gif.getCanvasWidth())/2;
    if (x_offset < 0) x_offset = 0;
    y_offset = (DISPLAY_HEIGHT - gif.getCanvasHeight())/2;
    if (y_offset < 0) y_offset = 0;
    Serial.printf("Successfully opened GIF; Canvas size = %d x %d\n", gif.getCanvasWidth(), gif.getCanvasHeight());
    Serial.flush();
    while (gif.playFrame(true, NULL))
    {      
    }
    gif.close();
  }

} /* ShowGIF() */

//
// Return true if a file's leaf name starts with a "." (it's been erased)
//
int ErasedFile(char *fname)
{
int iLen = strlen(fname);
int i;
  for (i=iLen-1; i>0; i--) // find start of leaf name
  {
    if (fname[i] == '/')
       break;
  }
  return (fname[i+1] == '.'); // found a dot?
}
void loop() {

char *szDir = "/GIF"; // play all GIFs in this directory on the SD card
char fname[256];
File root, temp;

   while (1) // run forever
   {
      root = SD.open(szDir);
      if (root)
      {
         temp = root.openNextFile();
            while (temp)
            {
              if (!temp.isDirectory()) // play it
              {
                strcpy(fname, temp.name());
                if (!ErasedFile(fname))
                {
                  Serial.printf("Playing %s\n", temp.name());
                  Serial.flush();
                  ShowGIF((char *)temp.name());
                }
              }
              temp.close();
              temp = root.openNextFile();
            }
         root.close();
      } // root
      delay(4000); // pause before restarting
   } // while
} /* loop() */

Serial Terminal

rst:0x10 (RTCWDT_RTC_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:1
load:0x3fff0018,len:4
load:0x3fff001c,len:1216
ho 0 tail 12 room 4
load:0x40078000,len:10944
load:0x40080400,len:6388
entry 0x400806b4
SD Card init success!
Playing /GIF/webview.gif
Successfully opened GIF; Canvas size = 128 x 160
Playing /GIF/contacts.gif
Successfully opened GIF; Canvas size = 128 x 128
Playing /GIF/weather.gif
Successfully opened GIF; Canvas size = 128 x 160
Playing /GIF/webview.gif
Successfully opened GIF; Canvas size = 128 x 160
Playing /GIF/contacts.gif
Successfully opened GIF; Canvas size = 128 x 128
Playing /GIF/weather.gif
Successfully opened GIF; Canvas size = 128 x 160
Playing /GIF/webview.gif
Successfully opened GIF; Canvas size = 128 x 160
Playing /GIF/contacts.gif
Successfully opened GIF; Canvas size = 128 x 128
Playing /GIF/weather.gif
Successfully opened GIF; Canvas size = 128 x 160
Playing /GIF/webview.gif
Successfully opened GIF; Canvas size = 128 x 160

I forgot, there is a compilation error in big_mem_demo too
this buffer is declared in the ucTXBuf example file, but it is also declared in the library, it was enough to change the name slightly and the problem was solved.

I am using the official library that is downloaded inside arduino 1.8.13

API Change in 1.4.0 breaks existing sketches

The change to AnimatedGIF::begin() removing iEndian is a large API change, and I believe along with deleting the definition for LITTLE_ENDIAN_PIXELS/BIG_ENDIAN_PIXELS broke a bunch of your example code. Maybe you should keep the endian definitions and the two-argument begin() calls around for backwards compatibility? I'm not sure how to support this change with my GifDecoder library. I'd probably have to note that earlier versions of my library are only compatible with AnimatedGIF <1.4.0 and later versions are only compatible with >=1.4.0

It's already causing problems for my GifDecoder Library users: pixelmatix/GifDecoder#8

/homer.gif where?

Sorry if this is the wrong place to ask a question. (Kinda' new to github, if confuses me a lot.)

Where can I find the actual file /homer.gif that the example for the SD drive is looking for?

-jim lee

error region ram overflowed by 16904 bytes STM32F103C8. it is compatible with STM32F103C8? File :adafruit_gfx_sdcard.ino

error region ram overflowed by 16904 bytes STM32F103C8. it is compatible with STM32F103C8?
File :adafruit_gfx_sdcard.ino

c:/users/jhonn/appdata/local/arduino15/packages/arduino/tools/arm-none-eabi-gcc/4.8.3-2014q1/bin/../lib/gcc/arm-none-eabi/4.8.3/../../../../arm-none-eabi/bin/ld.exe: C:\Users\jhonn\AppData\Local\Temp\arduino_build_218204/adafruit_gfx_sdcard.ino.elf section .text' will not fit in region rom'
c:/users/jhonn/appdata/local/arduino15/packages/arduino/tools/arm-none-eabi-gcc/4.8.3-2014q1/bin/../lib/gcc/arm-none-eabi/4.8.3/../../../../arm-none-eabi/bin/ld.exe: C:\Users\jhonn\AppData\Local\Temp\arduino_build_218204/adafruit_gfx_sdcard.ino.elf section .bss' will not fit in region ram'
c:/users/jhonn/appdata/local/arduino15/packages/arduino/tools/arm-none-eabi-gcc/4.8.3-2014q1/bin/../lib/gcc/arm-none-eabi/4.8.3/../../../../arm-none-eabi/bin/ld.exe**: region ram' overflowed by 16904 bytes** c:/users/jhonn/appdata/local/arduino15/packages/arduino/tools/arm-none-eabi-gcc/4.8.3-2014q1/bin/../lib/gcc/arm-none-eabi/4.8.3/../../../../arm-none-eabi/bin/ld.exe: **region rom' overflowed by 7964 bytes**
collect2.exe: error: ld returned 1 exit status
exit status 1

some gifs not rendering properly on hub75 panel using ESP32-HUB75-MatrixPanel-I2S-DMA library

hello, trying to figure out why some of the gifs i attempt to display on my hub75 64x32 rbg matrix panel are coming up oddly. see the attached files. first is a working gif. second is a non-working test gif and third is video showing how it is rendering on my panel. i have not tested these tho on any other display yet (such as a pyportal as the original animatedGIF library examples seem to be based upon).

centipede

Sprite-0001

Untitled.mov

i'm using code based on https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA/blob/master/examples/AnimatedGIFPanel/AnimatedGIFPanel.ino

Gif will not display on a ILI9341 tft

Hi,

I'm trying to play .gifs from an ESP32 (Adafruit Feather ESP32 v2) on a Adafruit ILI9341. I'd like to play .gifs from the ILI9341 built in SD card so I'm trying to use your Adafruit_gfx_sdcard example, (I have the Adafruit_gfx_memory example working fine with the above hardware).

The SDcard example however fails each time for me at the point it tries to open the .gif from the SD card. I'm assuming looking at the code that unlike the play from memory example you can run .gifs straight from the SD card without having to convert them into a .h file?

I've copied an extract below from the monitor. As you can see it mounts the card ok but fails to read anything on it. Tried changing/changing back the file path in line 188 so its in the route of the SD card but no luck. Considered it may be the .gif file itself but tried various ones, the current one I'm trying is a simple 320x240 3-frame gif.

It may (very well) be me doing something wrong but been at this all night now and its driving me up the wall. Any help/advice would be greatly appreciated. Thanks in advance.

rst:0x1 (POWERON_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
configsip: 271414342, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:1
load:0x3fff0030,len:1184
load:0x40078000,len:13160
load:0x40080400,len:3036
entry 0x400805e4
SD Card mount succeeded!
Error opening file = 5

`#include <AnimatedGIF.h>

#include "SPI.h"
#include "Adafruit_GFX.h"
#include "Adafruit_ILI9341.h"
#include <SD.h>

#define TFT_CS 14 //10
#define TFT_RST 15 //8
#define TFT_DC 32 //9
#define TFT_MOSI 19 //11
#define TFT_CLK 5 //13
#define TFT_MISO 21 //12

#define DISPLAY_WIDTH 320
#define DISPLAY_HEIGHT 240

#ifndef BUILTIN_SDCARD
#define BUILTIN_SDCARD 0
#endif

Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_MOSI, TFT_CLK, TFT_RST, TFT_MISO);
AnimatedGIF gif;
File f;

void * GIFOpenFile(const char *fname, int32_t *pSize)
{
f = SD.open(fname);
if (f)
{
*pSize = f.size();
return (void )&f;
}
return NULL;
} /
GIFOpenFile() */

void GIFCloseFile(void *pHandle)
{
File *f = static_cast<File >(pHandle);
if (f != NULL)
f->close();
} /
GIFCloseFile() */

int32_t GIFReadFile(GIFFILE *pFile, uint8_t *pBuf, int32_t iLen)
{
int32_t iBytesRead;
iBytesRead = iLen;
File *f = static_cast<File >(pFile->fHandle);
// Note: If you read a file all the way to the last byte, seek() stops working
if ((pFile->iSize - pFile->iPos) < iLen)
iBytesRead = pFile->iSize - pFile->iPos - 1; // <-- ugly work-around
if (iBytesRead <= 0)
return 0;
iBytesRead = (int32_t)f->read(pBuf, iBytesRead);
pFile->iPos = f->position();
return iBytesRead;
} /
GIFReadFile() */

int32_t GIFSeekFile(GIFFILE *pFile, int32_t iPosition)
{
int i = micros();
File *f = static_cast<File >(pFile->fHandle);
f->seek(iPosition);
pFile->iPos = (int32_t)f->position();
i = micros() - i;
// Serial.printf("Seek time = %d us\n", i);
return pFile->iPos;
} /
GIFSeekFile() */

// Draw a line of image directly on the LCD
void GIFDraw(GIFDRAW *pDraw)
{
uint8_t *s;
uint16_t *d, *usPalette, usTemp[320];
int x, y, iWidth;

iWidth = pDraw->iWidth;
if (iWidth + pDraw->iX > DISPLAY_WIDTH)
   iWidth = DISPLAY_WIDTH - pDraw->iX;
usPalette = pDraw->pPalette;
y = pDraw->iY + pDraw->y; // current line
if (y >= DISPLAY_HEIGHT || pDraw->iX >= DISPLAY_WIDTH || iWidth < 1)
   return; 
s = pDraw->pPixels;
if (pDraw->ucDisposalMethod == 2) // restore to background color
{
  for (x=0; x<iWidth; x++)
  {
    if (s[x] == pDraw->ucTransparent)
       s[x] = pDraw->ucBackground;
  }
  pDraw->ucHasTransparency = 0;
}

// Apply the new pixels to the main image
if (pDraw->ucHasTransparency) // if transparency used
{
  uint8_t *pEnd, c, ucTransparent = pDraw->ucTransparent;
  int x, iCount;
  pEnd = s + iWidth;
  x = 0;
  iCount = 0; // count non-transparent pixels
  while(x < iWidth)
  {
    c = ucTransparent-1;
    d = usTemp;
    while (c != ucTransparent && s < pEnd)
    {
      c = *s++;
      if (c == ucTransparent) // done, stop
      {
        s--; // back up to treat it like transparent
      }
      else // opaque
      {
         *d++ = usPalette[c];
         iCount++;
      }
    } // while looking for opaque pixels
    if (iCount) // any opaque pixels?
    {
      tft.startWrite();
      tft.setAddrWindow(pDraw->iX+x, y, iCount, 1);
      tft.writePixels(usTemp, iCount, false, false);
      tft.endWrite();
      x += iCount;
      iCount = 0;
    }
    // no, look for a run of transparent pixels
    c = ucTransparent;
    while (c == ucTransparent && s < pEnd)
    {
      c = *s++;
      if (c == ucTransparent)
         iCount++;
      else
         s--; 
    }
    if (iCount)
    {
      x += iCount; // skip these
      iCount = 0;
    }
  }
}
else
{
  s = pDraw->pPixels;
  // Translate the 8-bit pixels through the RGB565 palette (already byte reversed)
  for (x=0; x<iWidth; x++)
    usTemp[x] = usPalette[*s++];
  tft.startWrite();
  tft.setAddrWindow(pDraw->iX, y, iWidth, 1);
  tft.writePixels(usTemp, iWidth, false, false);
  tft.endWrite();
}

} /* GIFDraw() */

void setup() {
Serial.begin(115200);
while (!Serial);

// Note - some systems (ESP32?) require an SPI.begin() before calling SD.begin()
// this code was tested on a Teensy 4.1 board

if(!SD.begin(BUILTIN_SDCARD))
{
Serial.println("SD Card mount failed!");
return;
}
else
{
Serial.println("SD Card mount succeeded!");
}

// put your setup code here, to run once:
tft.begin();
tft.setRotation(1);
tft.fillScreen(ILI9341_BLACK);
gif.begin(LITTLE_ENDIAN_PIXELS);
}

void loop() {
// put your main code here, to run repeatedly:
// Serial.println("About to call gif.open");
if (gif.open("/GIF/Test.GIF", GIFOpenFile, GIFCloseFile, GIFReadFile, GIFSeekFile, GIFDraw))
{
GIFINFO gi;
Serial.printf("Successfully opened GIF; Canvas size = %d x %d\n", gif.getCanvasWidth(), gif.getCanvasHeight());
if (gif.getInfo(&gi)) {
Serial.printf("frame count: %d\n", gi.iFrameCount);
Serial.printf("duration: %d ms\n", gi.iDuration);
Serial.printf("max delay: %d ms\n", gi.iMaxDelay);
Serial.printf("min delay: %d ms\n", gi.iMinDelay);
}
while (gif.playFrame(true, NULL))
{
}
gif.close();
}
else
{
Serial.printf("Error opening file = %d\n", gif.getLastError());
while (1)
{};
}
}`

How can I skip frames faster in playFrame?

Hi. @bitbank2 I am @riraosan

I have been trying to find a way to draw a certain GIF animation at high speed by trial and error. However, I could not come up with a good idea. I have tried several fast rendering libraries, but I have to skip frames with frame waits less than 50ms.
How can I use a combination of playFrame and other APIs to skip frames with frame weights of 50ms or less at high speed?

Any advice would be appreciated.

Best Regards.

Some Problem GIFs

Working with the lib on Matrix Portal (RGB LED matrix), several small GIFs were having issues either with display glitches or altogether crashes. Wrestled with the Matrix-side code for a while before just trying the same GIFs over on PyPortal, where the player is known to work. Same issues there, so I’m wondering if this is a problem in AnimatedGIF. The images are unusually small (64x32 pixels) if that’s any indication. These all play fine on desktop system / in browser.

Juggler: glitches.
Lightspeed: crash; no frames displayed.
Werewolf: crash; initial frame displayed but nothing further.

These all look a little dark in browser because they’re manually gamma corrected for the way the LED matrix works…this is normal and known and shouldn’t affect anything.

juggler
lightspeed
werewolf

Slow down rendering?

Hi @bitbank2 ,

based on matrix i2s dma example sketch.
I have issue with gif bouncing right and left, the gif play ok/smooth in pc but in matrix it bouncing and some other gif,
seems the gif not finish slides from left to the correct position before the frame refresh. sometime i can see the gif slowly entering the frame from the left and then halfway new frame comes in.

i guess maybe the mcu cant keep up or out of ram.
is there way to slow down the rendering or put some delay to make sure proc have time to properly render frame by frame?
i have try to slow down the animation. put 0.2milis delay between frame but didnt work.

thanks in advanced

Slow image refresh rate

Sorry for putting this in the incorrect place before! Copying the statement:

"Thank you bit! I got it to work :) I am noticing that the frame refresh is quite slow, is there a way you recommend to speed it up? For reference I am using an ESP32.

also I had written a blurb about not getting the .h to work and I resolved that myself.

Thank you again! You are great!"

This is in reference to your [adafruit_gfx_memory.ino] code. Thank you in advanced, trying to learn as much as I can!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.