Skip to content

Commit

Permalink
Fixes and additional examples
Browse files Browse the repository at this point in the history
  • Loading branch information
Laurence Bank authored and Laurence Bank committed Mar 3, 2024
1 parent f107792 commit c9ee66b
Show file tree
Hide file tree
Showing 12 changed files with 124,643 additions and 93 deletions.
86 changes: 86 additions & 0 deletions examples/cyd_cooked_demo/cyd_cooked_demo.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
//
// This example shows how to use "cooked" pixel mode on the CYD (Cheap Yellow Display - an ESP32 + SPI LCD)
// With cooked pixels, we can send each line of pixels to the display without "jumping over" transparent pixels
// This almost always results in much higher display performance because switching between command and data mode
// on SPI displays can cause significant slowdowns.
//
#include <bb_spi_lcd.h>
#include <AnimatedGIF.h>
// Set the name of the example animation
#define GIF_NAME thisisfine_240x179
// Use any of the provided examples
#include "../test_images/thisisfine_240x179.h"

uint8_t *pFrameBuffer;

AnimatedGIF gif;
BB_SPI_LCD lcd;
int iOffX, iOffY;
//
// Draw callback from GIF decoder
//
// called once for each line of the current frame
// MCUs with very little RAM would have to test for disposal methods, transparent pixels
// and translate the 8-bit pixels through the palette to generate the final output.
// The code for MCUs with enough RAM is much simpler because the AnimatedGIF library can
// generate "cooked" pixels that are ready to send to the display
//
void GIFDraw(GIFDRAW *pDraw)
{
if (pDraw->y == 0) { // set the memory window when the first line is rendered
lcd.setAddrWindow(iOffX + pDraw->iX, iOffY + pDraw->iY, pDraw->iWidth, pDraw->iHeight);
}
// For all other lines, just push the pixels to the display
lcd.pushPixels((uint16_t *)pDraw->pPixels, pDraw->iWidth, DRAW_TO_LCD | DRAW_WITH_DMA);
} /* GIFDraw() */

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

lcd.begin(DISPLAY_CYD);
lcd.setRotation(0);
lcd.fillScreen(TFT_BLACK);
} /* setup() */

void loop() {
long lTime;
int iLoop, w, h, iFrames, iFPS;

gif.begin(GIF_PALETTE_RGB565_BE); // the SPI display's native pixel format is RGB565 big endian
if (gif.open((uint8_t *)GIF_NAME, sizeof(GIF_NAME), GIFDraw)) {
w = gif.getCanvasWidth();
h = gif.getCanvasHeight();
lcd.setTextColor(TFT_GREEN, TFT_BLACK);
lcd.setFont(FONT_12x16);
lcd.printf("GIF Canvas %dx%d\n", w, h);
// We need fast SRAM the size of the GIF canvas as 8-bit pixels + extra space for 1 line of cooked pixels (canvas width * sizeof(uint16_t))
pFrameBuffer = (uint8_t *)heap_caps_malloc(w * (h+2), MALLOC_CAP_8BIT);
if (pFrameBuffer == NULL) {
lcd.setTextColor(TFT_RED, TFT_BLACK);
lcd.println("Memory Error!");
lcd.printf("Failed to allocate %d bytes\n", w*(h+2));
lcd.println("Halted");
while (1) {}; // not enough memory to continue
}
iOffX = (lcd.width() - w)/2;
iOffY = (lcd.height() - h)/2;
iLoop = 0;
while (1) {
gif.setDrawType(GIF_DRAW_COOKED); // we want the library to generate ready-made pixels
gif.setFrameBuf(pFrameBuffer); // pass it the buffer to hold the canvas as 8-bit pixels (it merges new opaque pixels for each frame rendered)
lTime = micros();
iFrames = 0;
while (gif.playFrame(iLoop != 0, NULL)) { // Use the first loop to see the unthrottled library speed
iFrames++;
}
if (iLoop == 0) { // show the maximum possible render speed the first time through the animation loop
lTime = micros() - lTime;
iFPS = (iFrames * 10000000) / lTime;
lcd.setCursor(0, 304); // show speed at the bottom
lcd.printf("Max Speed: %d.%d FPS", iFPS/10, iFPS % 10);
}
gif.reset(); // don't close() the file here, just reset to frame 0 to repeat the sequence forever
iLoop++;
} // while (1)
} // if GIF opened successfully
} /* loop() */
79 changes: 79 additions & 0 deletions examples/turbo_t_qt_example/turbo_t_qt_example.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
//
// Example of how to use Turbo mode on the LilyGo T-QT
// This code can work equally well with a wide variety of MCUs and displays
//
#include <bb_spi_lcd.h>
#include <AnimatedGIF.h>
#define GIF_NAME earth_128x128
#include "../test_images/earth_128x128.h"

uint8_t *pTurboBuffer;

AnimatedGIF gif;
BB_SPI_LCD lcd;
int iOffX, iOffY;
//
// Draw callback from the AnimatedGIF decoder
//
// Called once for each line of the current frame
// MCUs with minimal RAM would have to process "RAW" pixels into "COOKED" here.
// "Cooking" involves testing for disposal methods, merging non-transparent pixels
// and translating the raw pixels through the palette to generate the final output.
// The code for MCUs with enough RAM is much simpler because the AnimatedGIF library can
// generate "cooked" pixels that are ready to send to the display as-is.
//
void GIFDraw(GIFDRAW *pDraw)
{
if (pDraw->y == 0) { // set the memory window (once per frame) when the first line is rendered
lcd.setAddrWindow(iOffX + pDraw->iX, iOffY + pDraw->iY, pDraw->iWidth, pDraw->iHeight);
}
// For all other lines, just push the pixels to the display. We requested 'COOKED'big-endian RGB565 and
// the library provides them here. No need to do anything except push them right to the display
lcd.pushPixels((uint16_t *)pDraw->pPixels, pDraw->iWidth, DRAW_TO_LCD | DRAW_WITH_DMA);
} /* GIFDraw() */

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

gif.begin(GIF_PALETTE_RGB565_BE); // Set the cooked output type we want (compatible with SPI LCDs)

// Take a look in bb_spi_lcd.h for a list of popular products which have a pre-configured display
// name. Alternatively, you can pass the library the GPIO pin numbers, but I made it easier
// for well-known IoT products by keeping those details inside my library.
lcd.begin(DISPLAY_T_QT); // Initialize the display controller of the LilyGo T-QT
lcd.fillScreen(TFT_BLACK);
} /* setup() */

void loop() {
long lTime;
int iFrames, iFPS;

// Allocate a buffer to enable Turbo decoding mode (~50% faster)
// it requires enough space for the full "raw" canvas plus about 32K workspace for the decoder
pTurboBuffer = (uint8_t *)heap_caps_malloc(TURBO_BUFFER_SIZE + (128*128), MALLOC_CAP_8BIT);

while (1) { // loop forever
// The GIFDraw callback is optional if you use Turbo mode (pass NULL to disable it). You can either
// manage the transparent pixels + palette conversion yourself or provide a framebuffer for the 'cooked'
// version of the canvas size (setDrawType to GIF_DRAW_FULLFRAME).
if (gif.open((uint8_t *)GIF_NAME, sizeof(GIF_NAME), GIFDraw)) {
Serial.printf("Successfully opened GIF; Canvas size = %d x %d\n", gif.getCanvasWidth(), gif.getCanvasHeight());
gif.setDrawType(GIF_DRAW_COOKED); // We want the library to generate ready-made pixels
gif.setTurboBuf(pTurboBuffer); // Set this before calling playFrame()
iOffX = (lcd.width() - gif.getCanvasWidth())/2; // center on the display
iOffY = (lcd.height() - gif.getCanvasHeight())/2;
lTime = micros();
// Decode frames until we hit the end of the file
// false in the first parameter tells it to return immediately so we can test the decode speed
// Change to true if you would like the animation to run at the speed programmed into the file
iFrames = 0;
while (gif.playFrame(false, NULL)) {
iFrames++;
}
lTime = micros() - lTime;
iFPS = (iFrames * 10000000) / lTime; // get 10x FPS to make an integer fraction of 1/10th
Serial.printf("total decode time for %d frames = %d us, %d.%d FPS\n", iFrames, (int)lTime, iFPS/10, iFPS % 10);
gif.close(); // You can also use gif.reset() instead of close() to start playing the same file again
} // if the file opened successfully
} // while (1)
} /* loop() */
22 changes: 20 additions & 2 deletions src/AnimatedGIF.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ int32_t iOldPos;
return (int)_gif.sCommentLen;
} /* getComment() */

//
//
// Allocate a block of memory to hold the entire canvas (as 8-bpp)
//
int AnimatedGIF::allocFrameBuf(GIF_ALLOC_CALLBACK *pfnAlloc)
Expand All @@ -85,6 +85,24 @@ int AnimatedGIF::allocFrameBuf(GIF_ALLOC_CALLBACK *pfnAlloc)
return GIF_INVALID_PARAMETER;
} /* allocFrameBuf() */
//
// Allocate a block of memory to hold the Turbo Buffer entire canvas (as 8-bpp)
// as well as 32k needed for faster decoding
//
int AnimatedGIF::allocTurboBuf(GIF_ALLOC_CALLBACK *pfnAlloc)
{
if (_gif.iCanvasWidth > 0 && _gif.iCanvasHeight > 0 && _gif.pTurboBuffer == NULL)
{
// Allocate a little extra space for the current line
// as RGB565 or RGB888
int iTurboSize = TURBO_BUFFER_SIZE + (_gif.iCanvasWidth * _gif.iCanvasHeight);
_gif.pTurboBuffer = (unsigned char *)(*pfnAlloc)(iTurboSize);
if (_gif.pTurboBuffer == NULL)
return GIF_ERROR_MEMORY;
return GIF_SUCCESS;
}
return GIF_INVALID_PARAMETER;
} /* allocTurboBuf() */
//
// Set the frame buffer pointer
//
void AnimatedGIF::setFrameBuf(void *pFrameBuf)
Expand All @@ -104,7 +122,7 @@ void AnimatedGIF::setTurboBuf(void *pBuf)
//
int AnimatedGIF::setDrawType(int iType)
{
if (iType != GIF_DRAW_RAW && iType != GIF_DRAW_COOKED && iType != GIF_DRAW_FULLFRAME)
if (iType != GIF_DRAW_RAW && iType != GIF_DRAW_COOKED)
return GIF_INVALID_PARAMETER; // invalid drawing mode
_gif.ucDrawType = (uint8_t)iType;
return GIF_SUCCESS;
Expand Down
37 changes: 23 additions & 14 deletions src/AnimatedGIF.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,25 +75,33 @@
#define LZW_BUF_SIZE_TURBO (LZW_BUF_SIZE + (2<<MAX_CODE_SIZE) + (PIXEL_LAST*2) + MAX_WIDTH)
#define LZW_HIGHWATER_TURBO ((LZW_BUF_SIZE_TURBO * 15) / 16)

//
// Pixel types
//
enum {
GIF_PALETTE_RGB565_LE = 0, // little endian (default)
GIF_PALETTE_RGB565_BE, // big endian
GIF_PALETTE_RGB888, // original 24-bpp entries
GIF_PALETTE_RGB8888, // Useful for 32-bit 'cooked' output
GIF_PALETTE_RGB8888, // 32-bit (alpha = 0xff)
};
// for compatibility with older code
#define LITTLE_ENDIAN_PIXELS GIF_PALETTE_RGB565_LE
#define BIG_ENDIAN_PIXELS GIF_PALETTE_RGB565_BE
//
// Draw callback pixel type
// RAW = 8-bit palettized pixels requiring transparent pixel handling
// COOKED = 16 or 24-bpp fully rendered pixels ready for display
// FULLFRAME = 16 or 24-bpp entire frame rendered with no callback
// Draw types
//
// RAW = 8-bit palettized pixels requiring transparent pixel handling and conversion through the palette.
// Each line is sent to the GIFDraw callback as 8-bit pixels. If a framebuffer exists, the lines will be
// written there too. The GIFDraw callback is optional if there is a framebuffer allocated.
//
// COOKED = 16/24/32-bpp fully rendered pixels ready for display. This requires a full frame buffer with extra
// room for the fully rendered pixels at the end of the 8-bit pixel buffer. For example, a 160x120
// canvas size with 24-bit output would require (160*120 + 3*160) bytes.
// Each prepared line is sent to the GIFDraw callback as a row of 16/24/32-bit pixels.
//
enum {
GIF_DRAW_RAW = 0,
GIF_DRAW_COOKED,
GIF_DRAW_FULLFRAME
GIF_DRAW_COOKED
};

enum {
Expand Down Expand Up @@ -130,6 +138,7 @@ typedef struct gif_draw_tag
int iX, iY; // Corner offset of this frame on the canvas
int y; // current line being drawn (0 = top line of image)
int iWidth, iHeight; // size of this frame
int iCanvasWidth; // need this to know where to place output in a fully cooked bitmap
void *pUser; // user supplied pointer
uint8_t *pPixels; // 8-bit source pixels for this line
uint16_t *pPalette; // little or big-endian RGB565 palette entries (default)
Expand All @@ -154,13 +163,13 @@ typedef void (GIF_FREE_CALLBACK)(void *buffer);
//
typedef struct gif_image_tag
{
int iWidth, iHeight, iCanvasWidth, iCanvasHeight;
int iX, iY; // GIF corner offset
int iBpp;
int iError; // last error
int iFrameDelay; // delay in milliseconds for this frame
int iRepeatCount; // NETSCAPE animation repeat count. 0=forever
int iXCount, iYCount; // decoding position in image (countdown values)
uint16_t iWidth, iHeight, iCanvasWidth, iCanvasHeight;
uint16_t iX, iY; // GIF corner offset
uint16_t iBpp;
int16_t iError; // last error
uint16_t iFrameDelay; // delay in milliseconds for this frame
uint16_t iRepeatCount; // NETSCAPE animation repeat count. 0=forever
uint16_t iXCount, iYCount; // decoding position in image (countdown values)
int iLZWOff; // current LZW data offset
int iLZWSize; // current quantity of data in the LZW buffer
int iCommentPos; // file offset of start of comment data
Expand Down
Loading

0 comments on commit c9ee66b

Please sign in to comment.