bitbank2 / animatedgif Goto Github PK
View Code? Open in Web Editor NEWAn optimized GIF decoder suitable for microcontrollers and PCs
License: Apache License 2.0
An optimized GIF decoder suitable for microcontrollers and PCs
License: Apache License 2.0
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.
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.
Also managed to produce a second GIF exhibiting the issue.
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.
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):
(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…
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…
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.
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!
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 :)
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.
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]
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.
Hello
You can make it work for STM32F401 and ST7789_NOCS
regards
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.
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!
Looks as though the repo location has changed recently in PlatformIO?
Formally:
bitbank2/AnimatedGIF
Now:
[email protected]/AnimatedGIF
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:
You can only change the GIF dimensions and number of frames in the pro version
Yes this is ugly but maybe it's useful to have a GIF with just two frames for brevity
There's trailing zeros left after the end of the GIF:
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;
}
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.
Hi how to connect
st7789 240*240 without cs pin and mini sd bb_spi_lcd
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
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
Hi
How did you create the gif.h
regards
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();
}
}
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:
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.
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:
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?
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
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;
}
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:
Any idea why it would hang? Again, there's no result from the function, just infinite delay.
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:
Thanks Larry.
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:
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.
You can use Samsung/rlottie library with C-api header
Ref:
Samsung/rlottie#484
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!
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:
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.
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()
)GIF_EMPTY_FRAME
error, and don't replace with GIF_SUCCESS
.
playFrame()
return of 0, and if they are, they can look for both GIF_EMPTY_FRAME
and GIF_SUCCESS
.getLastError()
to determine if there was a frame processedHi 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
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
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 >
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?
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.
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?
Hi
How did you create the gif.h
regards
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.
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
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
See subject :)
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
Is there a way to format the FB datas in 1555 instead of 565 or 888? My target FB has alpha channel.
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
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).
i'm using code based on https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA/blob/master/examples/AnimatedGIFPanel/AnimatedGIFPanel.ino
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)
{};
}
}`
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.
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.
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
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!
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.