Animated GIF example added

This commit is contained in:
mrfaptastic 2018-10-25 23:49:41 +01:00
parent 6836c4a563
commit 528c8ed561
13 changed files with 1811 additions and 13 deletions

View file

@ -61,7 +61,7 @@ void RGB64x32MatrixPanel_I2S_DMA::configureDMA()
Serial.printf("lsbMsbTransitionBit of %d gives %d Hz refresh: \r\n", lsbMsbTransitionBit, actualRefreshRate);
if (actualRefreshRate > 200) // HACK Hard Coded: Minimum frame rate of 160
if (actualRefreshRate > 250) // HACK Hard Coded: Minimum frame rate of 160
break;
@ -316,6 +316,127 @@ void RGB64x32MatrixPanel_I2S_DMA::updateMatrixDMABuffer(int16_t x_coord, int16_t
} // updateDMABuffer
void RGB64x32MatrixPanel_I2S_DMA::writeRGB24Frame2DMABuffer(rgb_24 *framedata, int16_t frame_width = MATRIX_WIDTH, int16_t frame_height = MATRIX_HEIGHT)
{
if ( !dma_configuration_success)
assert("DMA configuration in begin() not performed or completed successfully.");
for (unsigned int y = 0; y < ROWS_PER_FRAME; y++) // half height - 16 iterations
{
unsigned char currentRow = y;
for(int j = 0; j < COLOR_DEPTH_BITS; j++) // color depth - 8 iterations
{
uint16_t mask = (1 << (j)); // 24 bit color
//MATRIX_DATA_STORAGE_TYPE *p=matrixUpdateFrames[backbuf_id].rowdata[y].rowbits[pl].data; //matrixUpdateFrames
rowBitStruct *p=&matrixUpdateFrames[backbuf_id].rowdata[currentRow].rowbits[j]; //matrixUpdateFrames location to write to
int i=0;
while(i < PIXELS_PER_LATCH) // row pixels (64) iterations
{
for(int k=0; k < MATRIX_WIDTH; k++) // row pixel width 64 iterations
{
int v=0; // the output bitstream
//#if (CLKS_DURING_LATCH == 0)
// if there is no latch to hold address, output ADDX lines directly to GPIO and latch data at end of cycle
int gpioRowAddress = currentRow;
// normally output current rows ADDX, special case for LSB, output previous row's ADDX (as previous row is being displayed for one latch cycle)
if(j == 0)
gpioRowAddress = currentRow-1;
if (gpioRowAddress & 0x01) v|=BIT_A; // 1
if (gpioRowAddress & 0x02) v|=BIT_B; // 2
if (gpioRowAddress & 0x04) v|=BIT_C; // 4
if (gpioRowAddress & 0x08) v|=BIT_D; // 8
if (gpioRowAddress & 0x10) v|=BIT_E; // 16
// need to disable OE after latch to hide row transition
if((i+k) == 0) v|=BIT_OE;
// drive latch while shifting out last bit of RGB data
if((i+k) == PIXELS_PER_LATCH-1) v|=BIT_LAT;
//#endif
// turn off OE after brightness value is reached when displaying MSBs
// MSBs always output normal brightness
// LSB (!j) outputs normal brightness as MSB from previous row is being displayed
if((j > lsbMsbTransitionBit || !j) && ((i+k) >= brightness)) v|=BIT_OE;
// special case for the bits *after* LSB through (lsbMsbTransitionBit) - OE is output after data is shifted, so need to set OE to fractional brightness
if(j && j <= lsbMsbTransitionBit) {
// divide brightness in half for each bit below lsbMsbTransitionBit
int lsbBrightness = brightness >> (lsbMsbTransitionBit - j + 1);
if((i+k) >= lsbBrightness) v|=BIT_OE;
}
// need to turn off OE one clock before latch, otherwise can get ghosting
if((i+k)==PIXELS_PER_LATCH-1) v|=BIT_OE;
//#if 0
//
// int c1=getpixel(pix, k, y);
// int c2=getpixel(pix, k, y+(MATRIX_HEIGHT/2));
//
// if (c1 & (mask<<16)) v|=BIT_R1;
// if (c1 & (mask<<8)) v|=BIT_G1;
// if (c1 & (mask<<0)) v|=BIT_B1;
// if (c2 & (mask<<16)) v|=BIT_R2;
// if ( c2 & (mask<<8)) v|=BIT_G2;
// if (c2 & (mask<<0)) v|=BIT_B2;
//
//#else
struct rgb_24 c1( 255,0,0);
struct rgb_24 c2 = { 255,255,255 };
// struct rgb24 c2 = { 0,0,255 };
if (c1.red & mask)
v|=BIT_R1;
if (c1.green & mask)
v|=BIT_G1;
if (c1.blue & mask)
v|=BIT_B1;
if (c2.red & mask)
v|=BIT_R2;
if (c2.green & mask)
v|=BIT_G2;
if (c2.blue & mask)
v|=BIT_B2;
//#endif
// 16 bit parallel mode
//Save the calculated value to the bitplane memory in reverse order to account for I2S Tx FIFO mode1 ordering
if(k%2){
p->data[(i+k)-1] = v;
} else {
p->data[(i+k)+1] = v;
} // end reordering
} // end for MATRIX_WIDTH
i += MATRIX_WIDTH;
} // end pixels per latch loop (64)
} // color depth loop (8)
} // end half matrix length
//Show our work!
i2s_parallel_flip_to_buffer(&I2S1, backbuf_id);
swapBuffer();
} // updateDMABuffer

View file

@ -90,9 +90,9 @@
/***************************************************************************************/
/* HUB75 RGB Panel definitions and DMA Config. It's best you don't change any of this. */
#define MATRIX_HEIGHT 32
#define MATRIX_WIDTH 64
#define MATRIX_ROWS_IN_PARALLEL 2
#define MATRIX_HEIGHT 32
#define MATRIX_WIDTH 64
#define MATRIX_ROWS_IN_PARALLEL 2
// Panel Upper half RGB (numbering according to order in DMA gpio_bus configuration)
#define BIT_R1 (1<<0)
@ -116,8 +116,8 @@
#define BIT_E (1<<12)
// RGB Panel Constants / Calculated Values
#define PIXELS_PER_LATCH ((MATRIX_WIDTH * MATRIX_HEIGHT) / MATRIX_HEIGHT) // = 64
#define COLOR_CHANNELS_PER_PIXEL 3
#define PIXELS_PER_LATCH ((MATRIX_WIDTH * MATRIX_HEIGHT) / MATRIX_HEIGHT) // = 64
#define COLOR_DEPTH_BITS (COLOR_DEPTH/COLOR_CHANNELS_PER_PIXEL) // = 8
#define ROWS_PER_FRAME (MATRIX_HEIGHT/MATRIX_ROWS_IN_PARALLEL) // = 2
@ -150,6 +150,17 @@ struct frameStruct {
rowColorDepthStruct rowdata[ROWS_PER_FRAME];
};
typedef struct rgb_24 {
rgb_24() : rgb_24(0,0,0) {}
rgb_24(uint8_t r, uint8_t g, uint8_t b) {
red = r; green = g; blue = b;
}
rgb_24& operator=(const rgb_24& col);
uint8_t red;
uint8_t green;
uint8_t blue;
} rgb_24;
/***************************************************************************************/
class RGB64x32MatrixPanel_I2S_DMA : public Adafruit_GFX {
@ -158,18 +169,20 @@ class RGB64x32MatrixPanel_I2S_DMA : public Adafruit_GFX {
RGB64x32MatrixPanel_I2S_DMA(bool _doubleBuffer = false) // doublebuffer always enabled, option makes no difference
: Adafruit_GFX(MATRIX_WIDTH, MATRIX_HEIGHT), doubleBuffer(_doubleBuffer) {
allocateDMAbuffers();
backbuf_id = 0;
brightness = 64;
backbuf_id = 0;
brightness = 16;
}
void begin(void)
{
configureDMA(); //DMA and I2S configuration and setup
// Need to wipe the contents of the matrix buffers or weird things happen.
for (int y=0;y<MATRIX_HEIGHT; y++)
for (int x=0;x<MATRIX_WIDTH; x++)
updateMatrixDMABuffer( x, y, 0, 0, 0);
flushDMAbuffer();
swapBuffer();
flushDMAbuffer();
swapBuffer();
}
@ -177,6 +190,12 @@ class RGB64x32MatrixPanel_I2S_DMA : public Adafruit_GFX {
virtual void drawPixel(int16_t x, int16_t y, uint16_t color); // adafruit implementation
inline void drawPixelRGB565(int16_t x, int16_t y, uint16_t color);
inline void drawPixelRGB888(int16_t x, int16_t y, uint8_t r, uint8_t g, uint8_t b);
inline void drawPixelRGB24(int16_t x, int16_t y, rgb_24 color);
// TODO: Draw a frame! Oooh.
void writeRGB24Frame2DMABuffer(rgb_24 *framedata, int16_t frame_width, int16_t frame_height);
// Color 444 is a 4 bit scale, so 0 to 15, color 565 takes a 0-255 bit value, so scale up by 255/15 (i.e. 17)!
uint16_t color444(uint8_t r, uint8_t g, uint8_t b) { return color565(r*17,g*17,b*17); }
@ -203,6 +222,15 @@ class RGB64x32MatrixPanel_I2S_DMA : public Adafruit_GFX {
Serial.printf("Allocating Refresh Buffer:\r\nDMA Memory Available: %d bytes total, %d bytes largest free block: \r\n", heap_caps_get_free_size(MALLOC_CAP_DMA), heap_caps_get_largest_free_block(MALLOC_CAP_DMA));
} // end initMatrixDMABuffer()
void flushDMAbuffer()
{
Serial.printf("Flushing buffer %d", backbuf_id);
// Need to wipe the contents of the matrix buffers or weird things happen.
for (int y=0;y<MATRIX_HEIGHT; y++)
for (int x=0;x<MATRIX_WIDTH; x++)
updateMatrixDMABuffer( x, y, 0, 0, 0);
}
void configureDMA(); // Get everything setup. Refer to the .c file
@ -218,8 +246,8 @@ class RGB64x32MatrixPanel_I2S_DMA : public Adafruit_GFX {
// Pixel data is organized from LSB to MSB sequentially by row, from row 0 to row matrixHeight/matrixRowsInParallel (two rows of pixels are refreshed in parallel)
frameStruct *matrixUpdateFrames;
int lsbMsbTransitionBit;
int refreshRate;
int lsbMsbTransitionBit;
int refreshRate;
int backbuf_id; // which buffer is the DMA backbuffer, as in, which one is not active so we can write to it
int brightness;
@ -248,13 +276,21 @@ inline void RGB64x32MatrixPanel_I2S_DMA::drawPixelRGB888(int16_t x, int16_t y, u
updateMatrixDMABuffer( x, y, r, g, b);
}
inline void RGB64x32MatrixPanel_I2S_DMA::drawPixelRGB24(int16_t x, int16_t y, rgb_24 color)
{
updateMatrixDMABuffer( x, y, color.red, color.green, color.blue);
}
// Pass 8-bit (each) R,G,B, get back 16-bit packed color
//https://github.com/squix78/ILI9341Buffer/blob/master/ILI9341_SPI.cpp
inline uint16_t RGB64x32MatrixPanel_I2S_DMA::color565(uint8_t r, uint8_t g, uint8_t b) {
/*
Serial.printf("Got r value of %d\n", r);
Serial.printf("Got g value of %d\n", g);
Serial.printf("Got b value of %d\n", b);
*/
return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);
}

View file

@ -0,0 +1,127 @@
/*
* Written by: Craig A. Lindley
*
* Copyright (c) 2014 Craig A. Lindley
* Refactoring by Louis Beaudoin (Pixelmatix)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <ESP32-RGB64x32MatrixPanel-I2S-DMA.h>
#include <SPIFFS.h>
#include <Arduino.h>
RGB64x32MatrixPanel_I2S_DMA matrix;
#include "GifDecoder.h"
#include "FilenameFunctions.h"
#define GIF_DIRECTORY "/"
#define DISPLAY_TIME_SECONDS 5
const uint8_t kMatrixWidth = 64; // known working: 32, 64, 96, 128
const uint8_t kMatrixHeight = 32; // known working: 16, 32, 48, 64
/* template parameters are maxGifWidth, maxGifHeight, lzwMaxBits
*
* The lzwMaxBits value of 12 supports all GIFs, but uses 16kB RAM
* lzwMaxBits can be set to 10 or 11 for small displays, 12 for large displays
* All 32x32-pixel GIFs tested work with 11, most work with 10
*/
GifDecoder<kMatrixWidth, kMatrixHeight, 12> decoder;
int num_files;
void screenClearCallback(void) {
matrix.fillScreen(matrix.color565(0,0,0));
}
void updateScreenCallback(void) {
//backgroundLayer.swapBuffers();
}
void drawPixelCallback(int16_t x, int16_t y, uint8_t red, uint8_t green, uint8_t blue) {
matrix.drawPixel(x, y, matrix.color565(red, green, blue));
}
// Setup method runs once, when the sketch starts
void setup() {
Serial.begin(115200);
Serial.println("Starting AnimatedGIFs Sketch");
// Start filesystem
Serial.println(" * Loading SPIFFS");
if(!SPIFFS.begin()){
Serial.println("SPIFFS Mount Failed");
}
decoder.setScreenClearCallback(screenClearCallback);
decoder.setUpdateScreenCallback(updateScreenCallback);
decoder.setDrawPixelCallback(drawPixelCallback);
decoder.setFileSeekCallback(fileSeekCallback);
decoder.setFilePositionCallback(filePositionCallback);
decoder.setFileReadCallback(fileReadCallback);
decoder.setFileReadBlockCallback(fileReadBlockCallback);
matrix.begin();
// Clear screen
matrix.fillScreen(matrix.color565(0,0,0));
// Determine how many animated GIF files exist
num_files = enumerateGIFFiles(GIF_DIRECTORY, false);
if(num_files < 0) {
Serial.println("No gifs directory");
while(1);
}
if(!num_files) {
Serial.println("Empty gifs directory");
while(1);
}
}
int file_index = -1;
void loop() {
static unsigned long futureTime;
// int index = -1; //random(num_files);
if(futureTime < millis()) {
if (++file_index >= num_files) {
file_index = 0;
}
if (openGifFilenameByIndex(GIF_DIRECTORY, file_index) >= 0) {
// Can clear screen for new animation here, but this might cause flicker with short animations
// matrix.fillScreen(COLOR_BLACK);
// matrix.swapBuffers();
decoder.startDecoding();
// Calculate time in the future to terminate animation
futureTime = millis() + (DISPLAY_TIME_SECONDS * 1000);
}
}
decoder.decodeFrame();
}

View file

@ -0,0 +1,179 @@
// ADAPTED FROM: https://github.com/espressif/arduino-esp32/blob/master/libraries/WebServer/examples/FSBrowser/FSBrowser.ino
#define FILESYSTEM SPIFFS
#define FORMAT_FILESYSTEM true
#define DBG_OUTPUT_PORT Serial
//holds the current upload
File fsUploadFile;
//format bytes
String formatBytes(size_t bytes) {
if (bytes < 1024) {
return String(bytes) + "B";
} else if (bytes < (1024 * 1024)) {
return String(bytes / 1024.0) + "KB";
} else if (bytes < (1024 * 1024 * 1024)) {
return String(bytes / 1024.0 / 1024.0) + "MB";
} else {
return String(bytes / 1024.0 / 1024.0 / 1024.0) + "GB";
}
}
String getContentType(String filename) {
if (webServer.hasArg("download")) {
return "application/octet-stream";
} else if (filename.endsWith(".htm")) {
return "text/html";
} else if (filename.endsWith(".html")) {
return "text/html";
} else if (filename.endsWith(".css")) {
return "text/css";
} else if (filename.endsWith(".js")) {
return "application/javascript";
} else if (filename.endsWith(".png")) {
return "image/png";
} else if (filename.endsWith(".gif")) {
return "image/gif";
} else if (filename.endsWith(".jpg")) {
return "image/jpeg";
} else if (filename.endsWith(".ico")) {
return "image/x-icon";
} else if (filename.endsWith(".xml")) {
return "text/xml";
} else if (filename.endsWith(".pdf")) {
return "application/x-pdf";
} else if (filename.endsWith(".zip")) {
return "application/x-zip";
} else if (filename.endsWith(".gz")) {
return "application/x-gzip";
}
return "text/plain";
}
bool exists(String path){
bool yes = false;
File file = FILESYSTEM.open(path, "r");
if(!file.isDirectory()){
yes = true;
}
file.close();
return yes;
}
bool handleFileRead(String path) {
DBG_OUTPUT_PORT.println("handleFileRead: " + path);
if (path.endsWith("/")) {
path += "index.htm";
}
String contentType = getContentType(path);
String pathWithGz = path + ".gz";
if (exists(pathWithGz) || exists(path)) {
if (exists(pathWithGz)) {
path += ".gz";
}
File file = FILESYSTEM.open(path, "r");
webServer.streamFile(file, contentType);
file.close();
return true;
}
return false;
}
void handleFileUpload() {
if (webServer.uri() != "/edit") {
return;
}
HTTPUpload& upload = webServer.upload();
if (upload.status == UPLOAD_FILE_START) {
String filename = upload.filename;
if (!filename.startsWith("/")) {
filename = "/" + filename;
}
DBG_OUTPUT_PORT.print("handleFileUpload Name: "); DBG_OUTPUT_PORT.println(filename);
fsUploadFile = FILESYSTEM.open(filename, "w");
filename = String();
} else if (upload.status == UPLOAD_FILE_WRITE) {
//DBG_OUTPUT_PORT.print("handleFileUpload Data: "); DBG_OUTPUT_PORT.println(upload.currentSize);
if (fsUploadFile) {
fsUploadFile.write(upload.buf, upload.currentSize);
}
} else if (upload.status == UPLOAD_FILE_END) {
if (fsUploadFile) {
fsUploadFile.close();
}
DBG_OUTPUT_PORT.print("handleFileUpload Size: "); DBG_OUTPUT_PORT.println(upload.totalSize);
}
}
void handleFileDelete() {
if (webServer.args() == 0) {
return webServer.send(500, "text/plain", "BAD ARGS");
}
String path = webServer.arg(0);
DBG_OUTPUT_PORT.println("handleFileDelete: " + path);
if (path == "/") {
return webServer.send(500, "text/plain", "BAD PATH");
}
if (!exists(path)) {
return webServer.send(404, "text/plain", "FileNotFound");
}
FILESYSTEM.remove(path);
webServer.send(200, "text/plain", "");
path = String();
}
void handleFileCreate() {
if (webServer.args() == 0) {
return webServer.send(500, "text/plain", "BAD ARGS");
}
String path = webServer.arg(0);
DBG_OUTPUT_PORT.println("handleFileCreate: " + path);
if (path == "/") {
return webServer.send(500, "text/plain", "BAD PATH");
}
if (exists(path)) {
return webServer.send(500, "text/plain", "FILE EXISTS");
}
File file = FILESYSTEM.open(path, "w");
if (file) {
file.close();
} else {
return webServer.send(500, "text/plain", "CREATE FAILED");
}
webServer.send(200, "text/plain", "");
path = String();
}
void handleFileList() {
if (!webServer.hasArg("dir")) {
webServer.send(500, "text/plain", "BAD ARGS");
return;
}
String path = webServer.arg("dir");
DBG_OUTPUT_PORT.println("handleFileList: " + path);
File root = FILESYSTEM.open(path);
path = String();
String output = "[";
if(root.isDirectory()){
File file = root.openNextFile();
while(file){
if (output != "[") {
output += ',';
}
output += "{\"type\":\"";
output += (file.isDirectory()) ? "dir" : "file";
output += "\",\"name\":\"";
output += String(file.name()).substring(1);
output += "\"}";
file = root.openNextFile();
}
}
output += "]";
webServer.send(200, "text/json", output);
}

View file

@ -0,0 +1,169 @@
/*
* Animated GIFs Display Code for SmartMatrix and 32x32 RGB LED Panels
*
* This file contains code to enumerate and select animated GIF files by name
*
* Written by: Craig A. Lindley
*
* Other references: https://github.com/espressif/arduino-esp32/blob/master/libraries/WebServer/examples/FSBrowser/FSBrowser.ino
*
*/
#include "FilenameFunctions.h"
File file;
int numberOfFiles;
bool fileSeekCallback(unsigned long position) {
return file.seek(position);
}
unsigned long filePositionCallback(void) {
return file.position();
}
int fileReadCallback(void) {
return file.read();
}
int fileReadBlockCallback(void * buffer, int numberOfBytes) {
return file.read((uint8_t*)buffer, numberOfBytes);
}
bool isAnimationFile(const char filename []) {
String filenameString(filename);
#if defined(ESP32)
// ESP32 filename includes the full path, so need to remove the path before looking at the filename
int pathindex = filenameString.lastIndexOf("/");
if(pathindex >= 0)
filenameString.remove(0, pathindex + 1);
#endif
DBG_OUTPUT_PORT.print(filenameString);
if ((filenameString[0] == '_') || (filenameString[0] == '~') || (filenameString[0] == '.')) {
DBG_OUTPUT_PORT.println(" ignoring: leading _/~/. character");
return false;
}
filenameString.toUpperCase();
if (filenameString.endsWith(".GIF") != 1) {
DBG_OUTPUT_PORT.println(" ignoring: doesn't end of .GIF");
return false;
}
DBG_OUTPUT_PORT.println();
return true;
}
// Enumerate and possibly display the animated GIF filenames in GIFS directory
int enumerateGIFFiles(const char *directoryName, boolean displayFilenames) {
numberOfFiles = 0;
File directory = FILESYSTEM.open(directoryName);
if (!directory) {
return -1;
}
File file = directory.openNextFile();
while (file) {
if (isAnimationFile(file.name())) {
numberOfFiles++;
if (displayFilenames) {
DBG_OUTPUT_PORT.println(file.name());
}
}
file.close();
file = directory.openNextFile();
}
file.close();
directory.close();
return numberOfFiles;
}
// Get the full path/filename of the GIF file with specified index
void getGIFFilenameByIndex(const char *directoryName, int index, char *pnBuffer) {
char* filename;
// Make sure index is in range
if ((index < 0) || (index >= numberOfFiles))
return;
File directory = FILESYSTEM.open(directoryName);
if (!directory)
return;
File file = directory.openNextFile();
while (file && (index >= 0)) {
filename = (char*)file.name();
if (isAnimationFile(file.name())) {
index--;
#if !defined(ESP32)
// Copy the directory name into the pathname buffer - ESP32 SD Library includes the full path name in the filename, so no need to add the directory name
strcpy(pnBuffer, directoryName);
// Append the filename to the pathname
strcat(pnBuffer, filename);
#else
strcpy(pnBuffer, filename);
#endif
}
file.close();
file = directory.openNextFile();
}
file.close();
directory.close();
}
int openGifFilenameByIndex(const char *directoryName, int index) {
char pathname[30]; // long filename will break this... Smash the stack! i.e:
/*
* Stack smashing protect failure!
*
* abort() was called at PC 0x400d9a90 on core 1
*
* Backtrace: 0x40088578:0x3ffb1ec0 0x4008877b:0x3ffb1ee0 0x400d9a90:0x3ffb1f00 0x400d1d62:0x3ffb1f20 0x400d182f:0x3ffb1f80 0x400ef86e:0x3ffb1fa0
*
* Rebooting...
*/
getGIFFilenameByIndex(directoryName, index, pathname);
DBG_OUTPUT_PORT.print("Pathname: ");
DBG_OUTPUT_PORT.println(pathname);
if(file)
{
file.close();
DBG_OUTPUT_PORT.print("Closing old file...");
}
// Attempt to open the file for reading
DBG_OUTPUT_PORT.print("Opening new file...");
file = FILESYSTEM.open(pathname);
if (!file) {
DBG_OUTPUT_PORT.println("Error opening GIF file");
return -1;
}
return 0;
}
// Return a random animated gif path/filename from the specified directory
void chooseRandomGIFFilename(const char *directoryName, char *pnBuffer) {
int index = random(numberOfFiles);
getGIFFilenameByIndex(directoryName, index, pnBuffer);
}

View file

@ -0,0 +1,21 @@
#ifndef FILENAME_FUNCTIONS_H
#define FILENAME_FUNCTIONS_H
#include "FS.h" // for the 'File' class
#include <SPIFFS.h>
#define FILESYSTEM SPIFFS
#define DBG_OUTPUT_PORT Serial
int enumerateGIFFiles(const char *directoryName, boolean displayFilenames);
void getGIFFilenameByIndex(const char *directoryName, int index, char *pnBuffer);
int openGifFilenameByIndex(const char *directoryName, int index);
bool fileSeekCallback(unsigned long position);
unsigned long filePositionCallback(void);
int fileReadCallback(void);
int fileReadBlockCallback(void * buffer, int numberOfBytes);
#endif

View file

@ -0,0 +1,148 @@
#ifndef _GIFDECODER_H_
#define _GIFDECODER_H_
#include <stdint.h>
#include <Arduino.h>
typedef void (*callback)(void);
typedef void (*pixel_callback)(int16_t x, int16_t y, uint8_t red, uint8_t green, uint8_t blue);
typedef void* (*get_buffer_callback)(void);
typedef bool (*file_seek_callback)(unsigned long position);
typedef unsigned long (*file_position_callback)(void);
typedef int (*file_read_callback)(void);
typedef int (*file_read_block_callback)(void * buffer, int numberOfBytes);
// LZW constants
// NOTE: LZW_MAXBITS should be set to 10 or 11 for small displays, 12 for large displays
// all 32x32-pixel GIFs tested work with 11, most work with 10
// LZW_MAXBITS = 12 will support all GIFs, but takes 16kB RAM
#define LZW_SIZTABLE (1 << lzwMaxBits)
template <int maxGifWidth, int maxGifHeight, int lzwMaxBits>
class GifDecoder {
public:
int startDecoding(void);
int decodeFrame(void);
void setScreenClearCallback(callback f);
void setUpdateScreenCallback(callback f);
void setDrawPixelCallback(pixel_callback f);
void setStartDrawingCallback(callback f);
void setFileSeekCallback(file_seek_callback f);
void setFilePositionCallback(file_position_callback f);
void setFileReadCallback(file_read_callback f);
void setFileReadBlockCallback(file_read_block_callback f);
private:
void parseTableBasedImage(void);
void decompressAndDisplayFrame(unsigned long filePositionAfter);
int parseData(void);
int parseGIFFileTerminator(void);
void parseCommentExtension(void);
void parseApplicationExtension(void);
void parseGraphicControlExtension(void);
void parsePlainTextExtension(void);
void parseGlobalColorTable(void);
void parseLogicalScreenDescriptor(void);
bool parseGifHeader(void);
void copyImageDataRect(uint8_t *dst, uint8_t *src, int x, int y, int width, int height);
void fillImageData(uint8_t colorIndex);
void fillImageDataRect(uint8_t colorIndex, int x, int y, int width, int height);
int readIntoBuffer(void *buffer, int numberOfBytes);
int readWord(void);
void backUpStream(int n);
int readByte(void);
void lzw_decode_init(int csize);
int lzw_decode(uint8_t *buf, int len, uint8_t *bufend);
void lzw_setTempBuffer(uint8_t * tempBuffer);
int lzw_get_code(void);
// Logical screen descriptor attributes
int lsdWidth;
int lsdHeight;
int lsdPackedField;
int lsdAspectRatio;
int lsdBackgroundIndex;
// Table based image attributes
int tbiImageX;
int tbiImageY;
int tbiWidth;
int tbiHeight;
int tbiPackedBits;
bool tbiInterlaced;
int frameDelay;
int transparentColorIndex;
int prevBackgroundIndex;
int prevDisposalMethod;
int disposalMethod;
int lzwCodeSize;
bool keyFrame;
int rectX;
int rectY;
int rectWidth;
int rectHeight;
unsigned long nextFrameTime_ms;
int colorCount;
rgb_24 palette[256];
char tempBuffer[260];
// Buffer image data is decoded into
uint8_t imageData[maxGifWidth * maxGifHeight];
// Backup image data buffer for saving portions of image disposal method == 3
uint8_t imageDataBU[maxGifWidth * maxGifHeight];
callback screenClearCallback;
callback updateScreenCallback;
pixel_callback drawPixelCallback;
callback startDrawingCallback;
file_seek_callback fileSeekCallback;
file_position_callback filePositionCallback;
file_read_callback fileReadCallback;
file_read_block_callback fileReadBlockCallback;
// LZW variables
int bbits;
int bbuf;
int cursize; // The current code size
int curmask;
int codesize;
int clear_code;
int end_code;
int newcodes; // First available code
int top_slot; // Highest code for current size
int extra_slot;
int slot; // Last read code
int fc, oc;
int bs; // Current buffer size for GIF
int bcnt;
uint8_t *sp;
uint8_t * temp_buffer;
uint8_t stack [LZW_SIZTABLE];
uint8_t suffix [LZW_SIZTABLE];
uint16_t prefix [LZW_SIZTABLE];
// Masks for 0 .. 16 bits
unsigned int mask[17] = {
0x0000, 0x0001, 0x0003, 0x0007,
0x000F, 0x001F, 0x003F, 0x007F,
0x00FF, 0x01FF, 0x03FF, 0x07FF,
0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF,
0xFFFF
};
};
#include "GifDecoder_Impl.h"
#include "LzwDecoder_Impl.h"
#endif

View file

@ -0,0 +1,806 @@
/*
* This file contains code to parse animated GIF files
*
* Written by: Craig A. Lindley
*
* Copyright (c) 2014 Craig A. Lindley
* Minor modifications by Louis Beaudoin (pixelmatix)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#define GIFDEBUG 0
// This file contains C code, and ESP32 Arduino has changed to use the C++ template version of min()/max() which we can't use with C, so we can't depend on a #define min() from Arduino anymore
#ifndef min
#define min(a,b) ((a)<(b)?(a):(b))
#endif
#include "GifDecoder.h"
#if GIFDEBUG == 1
#define DEBUG_SCREEN_DESCRIPTOR 1
#define DEBUG_GLOBAL_COLOR_TABLE 1
#define DEBUG_PROCESSING_PLAIN_TEXT_EXT 1
#define DEBUG_PROCESSING_GRAPHIC_CONTROL_EXT 1
#define DEBUG_PROCESSING_APP_EXT 1
#define DEBUG_PROCESSING_COMMENT_EXT 1
#define DEBUG_PROCESSING_FILE_TERM 1
#define DEBUG_PROCESSING_TABLE_IMAGE_DESC 1
#define DEBUG_PROCESSING_TBI_DESC_START 1
#define DEBUG_PROCESSING_TBI_DESC_INTERLACED 1
#define DEBUG_PROCESSING_TBI_DESC_LOCAL_COLOR_TABLE 1
#define DEBUG_PROCESSING_TBI_DESC_LZWCODESIZE 1
#define DEBUG_PROCESSING_TBI_DESC_DATABLOCKSIZE 1
#define DEBUG_PROCESSING_TBI_DESC_LZWIMAGEDATA_OVERFLOW 1
#define DEBUG_PROCESSING_TBI_DESC_LZWIMAGEDATA_SIZE 1
#define DEBUG_PARSING_DATA 1
#define DEBUG_DECOMPRESS_AND_DISPLAY 1
#define DEBUG_WAIT_FOR_KEY_PRESS 0
#endif
#include "GifDecoder.h"
// Error codes
#define ERROR_NONE 0
#define ERROR_DONE_PARSING 1
#define ERROR_WAITING 2
#define ERROR_FILEOPEN -1
#define ERROR_FILENOTGIF -2
#define ERROR_BADGIFFORMAT -3
#define ERROR_UNKNOWNCONTROLEXT -4
#define GIFHDRTAGNORM "GIF87a" // tag in valid GIF file
#define GIFHDRTAGNORM1 "GIF89a" // tag in valid GIF file
#define GIFHDRSIZE 6
// Global GIF specific definitions
#define COLORTBLFLAG 0x80
#define INTERLACEFLAG 0x40
#define TRANSPARENTFLAG 0x01
#define NO_TRANSPARENT_INDEX -1
// Disposal methods
#define DISPOSAL_NONE 0
#define DISPOSAL_LEAVE 1
#define DISPOSAL_BACKGROUND 2
#define DISPOSAL_RESTORE 3
template <int maxGifWidth, int maxGifHeight, int lzwMaxBits>
void GifDecoder<maxGifWidth, maxGifHeight, lzwMaxBits>::setStartDrawingCallback(callback f) {
startDrawingCallback = f;
}
template <int maxGifWidth, int maxGifHeight, int lzwMaxBits>
void GifDecoder<maxGifWidth, maxGifHeight, lzwMaxBits>::setUpdateScreenCallback(callback f) {
updateScreenCallback = f;
}
template <int maxGifWidth, int maxGifHeight, int lzwMaxBits>
void GifDecoder<maxGifWidth, maxGifHeight, lzwMaxBits>::setDrawPixelCallback(pixel_callback f) {
drawPixelCallback = f;
}
template <int maxGifWidth, int maxGifHeight, int lzwMaxBits>
void GifDecoder<maxGifWidth, maxGifHeight, lzwMaxBits>::setScreenClearCallback(callback f) {
screenClearCallback = f;
}
template <int maxGifWidth, int maxGifHeight, int lzwMaxBits>
void GifDecoder<maxGifWidth, maxGifHeight, lzwMaxBits>::setFileSeekCallback(file_seek_callback f) {
fileSeekCallback = f;
}
template <int maxGifWidth, int maxGifHeight, int lzwMaxBits>
void GifDecoder<maxGifWidth, maxGifHeight, lzwMaxBits>::setFilePositionCallback(file_position_callback f) {
filePositionCallback = f;
}
template <int maxGifWidth, int maxGifHeight, int lzwMaxBits>
void GifDecoder<maxGifWidth, maxGifHeight, lzwMaxBits>::setFileReadCallback(file_read_callback f) {
fileReadCallback = f;
}
template <int maxGifWidth, int maxGifHeight, int lzwMaxBits>
void GifDecoder<maxGifWidth, maxGifHeight, lzwMaxBits>::setFileReadBlockCallback(file_read_block_callback f) {
fileReadBlockCallback = f;
}
// Backup the read stream by n bytes
template <int maxGifWidth, int maxGifHeight, int lzwMaxBits>
void GifDecoder<maxGifWidth, maxGifHeight, lzwMaxBits>::backUpStream(int n) {
fileSeekCallback(filePositionCallback() - n);
}
// Read a file byte
template <int maxGifWidth, int maxGifHeight, int lzwMaxBits>
int GifDecoder<maxGifWidth, maxGifHeight, lzwMaxBits>::readByte() {
int b = fileReadCallback();
if (b == -1) {
#if GIFDEBUG == 1
Serial.println("Read error or EOF occurred");
#endif
}
return b;
}
// Read a file word
template <int maxGifWidth, int maxGifHeight, int lzwMaxBits>
int GifDecoder<maxGifWidth, maxGifHeight, lzwMaxBits>::readWord() {
int b0 = readByte();
int b1 = readByte();
return (b1 << 8) | b0;
}
// Read the specified number of bytes into the specified buffer
template <int maxGifWidth, int maxGifHeight, int lzwMaxBits>
int GifDecoder<maxGifWidth, maxGifHeight, lzwMaxBits>::readIntoBuffer(void *buffer, int numberOfBytes) {
int result = fileReadBlockCallback(buffer, numberOfBytes);
if (result == -1) {
Serial.println("Read error or EOF occurred");
}
return result;
}
// Fill a portion of imageData buffer with a color index
template <int maxGifWidth, int maxGifHeight, int lzwMaxBits>
void GifDecoder<maxGifWidth, maxGifHeight, lzwMaxBits>::fillImageDataRect(uint8_t colorIndex, int x, int y, int width, int height) {
int yOffset;
for (int yy = y; yy < height + y; yy++) {
yOffset = yy * maxGifWidth;
for (int xx = x; xx < width + x; xx++) {
imageData[yOffset + xx] = colorIndex;
}
}
}
// Fill entire imageData buffer with a color index
template <int maxGifWidth, int maxGifHeight, int lzwMaxBits>
void GifDecoder<maxGifWidth, maxGifHeight, lzwMaxBits>::fillImageData(uint8_t colorIndex) {
memset(imageData, colorIndex, sizeof(imageData));
}
// Copy image data in rect from a src to a dst
template <int maxGifWidth, int maxGifHeight, int lzwMaxBits>
void GifDecoder<maxGifWidth, maxGifHeight, lzwMaxBits>::copyImageDataRect(uint8_t *dst, uint8_t *src, int x, int y, int width, int height) {
int yOffset, offset;
for (int yy = y; yy < height + y; yy++) {
yOffset = yy * maxGifWidth;
for (int xx = x; xx < width + x; xx++) {
offset = yOffset + xx;
dst[offset] = src[offset];
}
}
}
// Make sure the file is a Gif file
template <int maxGifWidth, int maxGifHeight, int lzwMaxBits>
bool GifDecoder<maxGifWidth, maxGifHeight, lzwMaxBits>::parseGifHeader() {
char buffer[10];
readIntoBuffer(buffer, GIFHDRSIZE);
if ((strncmp(buffer, GIFHDRTAGNORM, GIFHDRSIZE) != 0) &&
(strncmp(buffer, GIFHDRTAGNORM1, GIFHDRSIZE) != 0)) {
return false;
}
else {
return true;
}
}
// Parse the logical screen descriptor
template <int maxGifWidth, int maxGifHeight, int lzwMaxBits>
void GifDecoder<maxGifWidth, maxGifHeight, lzwMaxBits>::parseLogicalScreenDescriptor() {
lsdWidth = readWord();
lsdHeight = readWord();
lsdPackedField = readByte();
lsdBackgroundIndex = readByte();
lsdAspectRatio = readByte();
#if GIFDEBUG == 1 && DEBUG_SCREEN_DESCRIPTOR == 1
Serial.print("lsdWidth: ");
Serial.println(lsdWidth);
Serial.print("lsdHeight: ");
Serial.println(lsdHeight);
Serial.print("lsdPackedField: ");
Serial.println(lsdPackedField, HEX);
Serial.print("lsdBackgroundIndex: ");
Serial.println(lsdBackgroundIndex);
Serial.print("lsdAspectRatio: ");
Serial.println(lsdAspectRatio);
#endif
}
// Parse the global color table
template <int maxGifWidth, int maxGifHeight, int lzwMaxBits>
void GifDecoder<maxGifWidth, maxGifHeight, lzwMaxBits>::parseGlobalColorTable() {
// Does a global color table exist?
if (lsdPackedField & COLORTBLFLAG) {
// A GCT was present determine how many colors it contains
colorCount = 1 << ((lsdPackedField & 7) + 1);
#if GIFDEBUG == 1 && DEBUG_GLOBAL_COLOR_TABLE == 1
Serial.print("Global color table with ");
Serial.print(colorCount);
Serial.println(" colors present");
#endif
// Read color values into the palette array
int colorTableBytes = sizeof(rgb_24) * colorCount;
readIntoBuffer(palette, colorTableBytes);
}
}
// Parse plain text extension and dispose of it
template <int maxGifWidth, int maxGifHeight, int lzwMaxBits>
void GifDecoder<maxGifWidth, maxGifHeight, lzwMaxBits>::parsePlainTextExtension() {
#if GIFDEBUG == 1 && DEBUG_PROCESSING_PLAIN_TEXT_EXT == 1
Serial.println("\nProcessing Plain Text Extension");
#endif
// Read plain text header length
uint8_t len = readByte();
// Consume plain text header data
readIntoBuffer(tempBuffer, len);
// Consume the plain text data in blocks
len = readByte();
while (len != 0) {
readIntoBuffer(tempBuffer, len);
len = readByte();
}
}
// Parse a graphic control extension
template <int maxGifWidth, int maxGifHeight, int lzwMaxBits>
void GifDecoder<maxGifWidth, maxGifHeight, lzwMaxBits>::parseGraphicControlExtension() {
#if GIFDEBUG == 1 && DEBUG_PROCESSING_GRAPHIC_CONTROL_EXT == 1
Serial.println("\nProcessing Graphic Control Extension");
#endif
int len = readByte(); // Check length
if (len != 4) {
Serial.println("Bad graphic control extension");
}
int packedBits = readByte();
frameDelay = readWord();
transparentColorIndex = readByte();
if ((packedBits & TRANSPARENTFLAG) == 0) {
// Indicate no transparent index
transparentColorIndex = NO_TRANSPARENT_INDEX;
}
disposalMethod = (packedBits >> 2) & 7;
if (disposalMethod > 3) {
disposalMethod = 0;
Serial.println("Invalid disposal value");
}
readByte(); // Toss block end
#if GIFDEBUG == 1 && DEBUG_PROCESSING_GRAPHIC_CONTROL_EXT == 1
Serial.print("PacketBits: ");
Serial.println(packedBits, HEX);
Serial.print("Frame delay: ");
Serial.println(frameDelay);
Serial.print("transparentColorIndex: ");
Serial.println(transparentColorIndex);
Serial.print("disposalMethod: ");
Serial.println(disposalMethod);
#endif
}
// Parse application extension
template <int maxGifWidth, int maxGifHeight, int lzwMaxBits>
void GifDecoder<maxGifWidth, maxGifHeight, lzwMaxBits>::parseApplicationExtension() {
memset(tempBuffer, 0, sizeof(tempBuffer));
#if GIFDEBUG == 1 && DEBUG_PROCESSING_APP_EXT == 1
Serial.println("\nProcessing Application Extension");
#endif
// Read block length
uint8_t len = readByte();
// Read app data
readIntoBuffer(tempBuffer, len);
#if GIFDEBUG == 1 && DEBUG_PROCESSING_APP_EXT == 1
// Conditionally display the application extension string
if (strlen(tempBuffer) != 0) {
Serial.print("Application Extension: ");
Serial.println(tempBuffer);
}
#endif
// Consume any additional app data
len = readByte();
while (len != 0) {
readIntoBuffer(tempBuffer, len);
len = readByte();
}
}
// Parse comment extension
template <int maxGifWidth, int maxGifHeight, int lzwMaxBits>
void GifDecoder<maxGifWidth, maxGifHeight, lzwMaxBits>::parseCommentExtension() {
#if GIFDEBUG == 1 && DEBUG_PROCESSING_COMMENT_EXT == 1
Serial.println("\nProcessing Comment Extension");
#endif
// Read block length
uint8_t len = readByte();
while (len != 0) {
// Clear buffer
memset(tempBuffer, 0, sizeof(tempBuffer));
// Read len bytes into buffer
readIntoBuffer(tempBuffer, len);
#if GIFDEBUG == 1 && DEBUG_PROCESSING_COMMENT_EXT == 1
// Display the comment extension string
if (strlen(tempBuffer) != 0) {
Serial.print("Comment Extension: ");
Serial.println(tempBuffer);
}
#endif
// Read the new block length
len = readByte();
}
}
// Parse file terminator
template <int maxGifWidth, int maxGifHeight, int lzwMaxBits>
int GifDecoder<maxGifWidth, maxGifHeight, lzwMaxBits>::parseGIFFileTerminator() {
#if GIFDEBUG == 1 && DEBUG_PROCESSING_FILE_TERM == 1
Serial.println("\nProcessing file terminator");
#endif
uint8_t b = readByte();
if (b != 0x3B) {
#if GIFDEBUG == 1 && DEBUG_PROCESSING_FILE_TERM == 1
Serial.print("Terminator byte: ");
Serial.println(b, HEX);
#endif
Serial.println("Bad GIF file format - Bad terminator");
return ERROR_BADGIFFORMAT;
}
else {
return ERROR_NONE;
}
}
// Parse table based image data
template <int maxGifWidth, int maxGifHeight, int lzwMaxBits>
void GifDecoder<maxGifWidth, maxGifHeight, lzwMaxBits>::parseTableBasedImage() {
#if GIFDEBUG == 1 && DEBUG_PROCESSING_TBI_DESC_START == 1
Serial.println("\nProcessing Table Based Image Descriptor");
#endif
#if GIFDEBUG == 1 && DEBUG_PARSING_DATA == 1
Serial.println("File Position: ");
Serial.println(filePositionCallback());
Serial.println("File Size: ");
//Serial.println(file.size());
#endif
// Parse image descriptor
tbiImageX = readWord();
tbiImageY = readWord();
tbiWidth = readWord();
tbiHeight = readWord();
tbiPackedBits = readByte();
#if GIFDEBUG == 1
Serial.print("tbiImageX: ");
Serial.println(tbiImageX);
Serial.print("tbiImageY: ");
Serial.println(tbiImageY);
Serial.print("tbiWidth: ");
Serial.println(tbiWidth);
Serial.print("tbiHeight: ");
Serial.println(tbiHeight);
Serial.print("PackedBits: ");
Serial.println(tbiPackedBits, HEX);
#endif
// Is this image interlaced ?
tbiInterlaced = ((tbiPackedBits & INTERLACEFLAG) != 0);
#if GIFDEBUG == 1 && DEBUG_PROCESSING_TBI_DESC_INTERLACED == 1
Serial.print("Image interlaced: ");
Serial.println((tbiInterlaced != 0) ? "Yes" : "No");
#endif
// Does this image have a local color table ?
bool localColorTable = ((tbiPackedBits & COLORTBLFLAG) != 0);
if (localColorTable) {
int colorBits = ((tbiPackedBits & 7) + 1);
colorCount = 1 << colorBits;
#if GIFDEBUG == 1 && DEBUG_PROCESSING_TBI_DESC_LOCAL_COLOR_TABLE == 1
Serial.print("Local color table with ");
Serial.print(colorCount);
Serial.println(" colors present");
#endif
// Read colors into palette
int colorTableBytes = sizeof(rgb_24) * colorCount;
readIntoBuffer(palette, colorTableBytes);
}
// One time initialization of imageData before first frame
if (keyFrame) {
if (transparentColorIndex == NO_TRANSPARENT_INDEX) {
fillImageData(lsdBackgroundIndex);
}
else {
fillImageData(transparentColorIndex);
}
keyFrame = false;
rectX = 0;
rectY = 0;
rectWidth = maxGifWidth;
rectHeight = maxGifHeight;
}
// Don't clear matrix screen for these disposal methods
if ((prevDisposalMethod != DISPOSAL_NONE) && (prevDisposalMethod != DISPOSAL_LEAVE)) {
if(screenClearCallback)
(*screenClearCallback)();
}
// Process previous disposal method
if (prevDisposalMethod == DISPOSAL_BACKGROUND) {
// Fill portion of imageData with previous background color
fillImageDataRect(prevBackgroundIndex, rectX, rectY, rectWidth, rectHeight);
}
else if (prevDisposalMethod == DISPOSAL_RESTORE) {
copyImageDataRect(imageData, imageDataBU, rectX, rectY, rectWidth, rectHeight);
}
// Save disposal method for this frame for next time
prevDisposalMethod = disposalMethod;
if (disposalMethod != DISPOSAL_NONE) {
// Save dimensions of this frame
rectX = tbiImageX;
rectY = tbiImageY;
rectWidth = tbiWidth;
rectHeight = tbiHeight;
// limit rectangle to the bounds of maxGifWidth*maxGifHeight
if(rectX + rectWidth > maxGifWidth)
rectWidth = maxGifWidth-rectX;
if(rectY + rectHeight > maxGifHeight)
rectHeight = maxGifHeight-rectY;
if(rectX >= maxGifWidth || rectY >= maxGifHeight) {
rectX = rectY = rectWidth = rectHeight = 0;
}
if (disposalMethod == DISPOSAL_BACKGROUND) {
if (transparentColorIndex != NO_TRANSPARENT_INDEX) {
prevBackgroundIndex = transparentColorIndex;
}
else {
prevBackgroundIndex = lsdBackgroundIndex;
}
}
else if (disposalMethod == DISPOSAL_RESTORE) {
copyImageDataRect(imageDataBU, imageData, rectX, rectY, rectWidth, rectHeight);
}
}
// Read the min LZW code size
lzwCodeSize = readByte();
#if GIFDEBUG == 1 && DEBUG_PROCESSING_TBI_DESC_LZWCODESIZE == 1
Serial.print("LzwCodeSize: ");
Serial.println(lzwCodeSize);
Serial.println("File Position Before: ");
Serial.println(filePositionCallback());
#endif
unsigned long filePositionBefore = filePositionCallback();
// Gather the lzw image data
// NOTE: the dataBlockSize byte is left in the data as the lzw decoder needs it
int offset = 0;
int dataBlockSize = readByte();
while (dataBlockSize != 0) {
#if GIFDEBUG == 1 && DEBUG_PROCESSING_TBI_DESC_DATABLOCKSIZE == 1
Serial.print("dataBlockSize: ");
Serial.println(dataBlockSize);
#endif
backUpStream(1);
dataBlockSize++;
fileSeekCallback(filePositionCallback() + dataBlockSize);
offset += dataBlockSize;
dataBlockSize = readByte();
}
#if GIFDEBUG == 1 && DEBUG_PROCESSING_TBI_DESC_LZWIMAGEDATA_SIZE == 1
Serial.print("total lzwImageData Size: ");
Serial.println(offset);
Serial.println("File Position Test: ");
Serial.println(filePositionCallback());
#endif
// this is the position where GIF decoding needs to pick up after decompressing frame
unsigned long filePositionAfter = filePositionCallback();
fileSeekCallback(filePositionBefore);
// Process the animation frame for display
// Initialize the LZW decoder for this frame
lzw_decode_init(lzwCodeSize);
lzw_setTempBuffer((uint8_t*)tempBuffer);
// Make sure there is at least some delay between frames
if (frameDelay < 1) {
frameDelay = 1;
}
// Decompress LZW data and display the frame
decompressAndDisplayFrame(filePositionAfter);
// Graphic control extension is for a single frame
transparentColorIndex = NO_TRANSPARENT_INDEX;
disposalMethod = DISPOSAL_NONE;
}
// Parse gif data
template <int maxGifWidth, int maxGifHeight, int lzwMaxBits>
int GifDecoder<maxGifWidth, maxGifHeight, lzwMaxBits>::parseData() {
if(nextFrameTime_ms > millis())
return ERROR_WAITING;
#if GIFDEBUG == 1 && DEBUG_PARSING_DATA == 1
Serial.println("\nParsing Data Block");
#endif
bool parsedFrame = false;
while (!parsedFrame) {
#if GIFDEBUG == 1 && DEBUG_WAIT_FOR_KEY_PRESS == 1
Serial.println("\nPress Key For Next");
while(Serial.read() <= 0);
#endif
// Determine what kind of data to process
uint8_t b = readByte();
if (b == 0x2c) {
// Parse table based image
#if GIFDEBUG == 1 && DEBUG_PARSING_DATA == 1
Serial.println("\nParsing Table Based");
#endif
parseTableBasedImage();
parsedFrame = true;
}
else if (b == 0x21) {
// Parse extension
b = readByte();
#if GIFDEBUG == 1 && DEBUG_PARSING_DATA == 1
Serial.println("\nParsing Extension");
#endif
// Determine which kind of extension to parse
switch (b) {
case 0x01:
// Plain test extension
parsePlainTextExtension();
break;
case 0xf9:
// Graphic control extension
parseGraphicControlExtension();
break;
case 0xfe:
// Comment extension
parseCommentExtension();
break;
case 0xff:
// Application extension
parseApplicationExtension();
break;
default:
Serial.print("Unknown control extension: ");
Serial.println(b, HEX);
return ERROR_UNKNOWNCONTROLEXT;
}
}
else {
#if GIFDEBUG == 1 && DEBUG_PARSING_DATA == 1
Serial.println("\nParsing Done");
#endif
// Push unprocessed byte back into the stream for later processing
backUpStream(1);
return ERROR_DONE_PARSING;
}
}
return ERROR_NONE;
}
template <int maxGifWidth, int maxGifHeight, int lzwMaxBits>
int GifDecoder<maxGifWidth, maxGifHeight, lzwMaxBits>::startDecoding(void) {
// Initialize variables
keyFrame = true;
prevDisposalMethod = DISPOSAL_NONE;
transparentColorIndex = NO_TRANSPARENT_INDEX;
nextFrameTime_ms = 0;
fileSeekCallback(0);
// Validate the header
if (! parseGifHeader()) {
Serial.println("Not a GIF file");
return ERROR_FILENOTGIF;
}
// If we get here we have a gif file to process
// Parse the logical screen descriptor
parseLogicalScreenDescriptor();
// Parse the global color table
parseGlobalColorTable();
return ERROR_NONE;
}
template <int maxGifWidth, int maxGifHeight, int lzwMaxBits>
int GifDecoder<maxGifWidth, maxGifHeight, lzwMaxBits>::decodeFrame(void) {
// Parse gif data
int result = parseData();
if (result < ERROR_NONE) {
Serial.println("Error: ");
Serial.println(result);
Serial.println(" occurred during parsing of data");
return result;
}
if (result == ERROR_DONE_PARSING) {
//startDecoding();
// Initialize variables like with a new file
keyFrame = true;
prevDisposalMethod = DISPOSAL_NONE;
transparentColorIndex = NO_TRANSPARENT_INDEX;
nextFrameTime_ms = 0;
fileSeekCallback(0);
// parse Gif Header like with a new file
parseGifHeader();
// Parse the logical screen descriptor
parseLogicalScreenDescriptor();
// Parse the global color table
parseGlobalColorTable();
}
return result;
}
// Decompress LZW data and display animation frame
template <int maxGifWidth, int maxGifHeight, int lzwMaxBits>
void GifDecoder<maxGifWidth, maxGifHeight, lzwMaxBits>::decompressAndDisplayFrame(unsigned long filePositionAfter) {
// Each pixel of image is 8 bits and is an index into the palette
// How the image is decoded depends upon whether it is interlaced or not
// Decode the interlaced LZW data into the image buffer
if (tbiInterlaced) {
// Decode every 8th line starting at line 0
for (int line = tbiImageY + 0; line < tbiHeight + tbiImageY; line += 8) {
lzw_decode(imageData + (line * maxGifWidth) + tbiImageX, tbiWidth, min(imageData + (line * maxGifWidth) + maxGifWidth, imageData + sizeof(imageData)));
}
// Decode every 8th line starting at line 4
for (int line = tbiImageY + 4; line < tbiHeight + tbiImageY; line += 8) {
lzw_decode(imageData + (line * maxGifWidth) + tbiImageX, tbiWidth, min(imageData + (line * maxGifWidth) + maxGifWidth, imageData + sizeof(imageData)));
}
// Decode every 4th line starting at line 2
for (int line = tbiImageY + 2; line < tbiHeight + tbiImageY; line += 4) {
lzw_decode(imageData + (line * maxGifWidth) + tbiImageX, tbiWidth, min(imageData + (line * maxGifWidth) + maxGifWidth, imageData + sizeof(imageData)));
}
// Decode every 2nd line starting at line 1
for (int line = tbiImageY + 1; line < tbiHeight + tbiImageY; line += 2) {
lzw_decode(imageData + (line * maxGifWidth) + tbiImageX, tbiWidth, min(imageData + (line * maxGifWidth) + maxGifWidth, imageData + sizeof(imageData)));
}
}
else {
// Decode the non interlaced LZW data into the image data buffer
for (int line = tbiImageY; line < tbiHeight + tbiImageY; line++) {
lzw_decode(imageData + (line * maxGifWidth) + tbiImageX, tbiWidth, imageData + sizeof(imageData));
}
}
#if GIFDEBUG == 1 && DEBUG_DECOMPRESS_AND_DISPLAY == 1
Serial.println("File Position After: ");
Serial.println(filePositionCallback());
#endif
#if GIFDEBUG == 1 && DEBUG_WAIT_FOR_KEY_PRESS == 1
Serial.println("\nPress Key For Next");
while(Serial.read() <= 0);
#endif
// LZW doesn't parse through all the data, manually set position
fileSeekCallback(filePositionAfter);
// Optional callback can be used to get drawing routines ready
if(startDrawingCallback)
(*startDrawingCallback)();
// Image data is decompressed, now display portion of image affected by frame
int yOffset, pixel;
for (int y = tbiImageY; y < tbiHeight + tbiImageY; y++) {
yOffset = y * maxGifWidth;
for (int x = tbiImageX; x < tbiWidth + tbiImageX; x++) {
// Get the next pixel
pixel = imageData[yOffset + x];
// Check pixel transparency
if (pixel == transparentColorIndex) {
continue;
}
// Pixel not transparent so get color from palette and draw the pixel
if(drawPixelCallback)
(*drawPixelCallback)(x, y, palette[pixel].red, palette[pixel].green, palette[pixel].blue);
}
}
// Make animation frame visible
// swapBuffers() call can take up to 1/framerate seconds to return (it waits until a buffer copy is complete)
// note the time before calling
// wait until time to display next frame
while(nextFrameTime_ms > millis());
// calculate time to display next frame
nextFrameTime_ms = millis() + (10 * frameDelay);
if(updateScreenCallback)
(*updateScreenCallback)();
}

View file

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014 Craig A. Lindley
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View file

@ -0,0 +1,169 @@
/*
* This file contains code to decompress the LZW encoded animated GIF data
*
* Written by: Craig A. Lindley, Fabrice Bellard and Steven A. Bennett
* See my book, "Practical Image Processing in C", John Wiley & Sons, Inc.
*
* Copyright (c) 2014 Craig A. Lindley
* Minor modifications by Louis Beaudoin (pixelmatix)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#define LZWDEBUG 0
#include "GifDecoder.h"
template <int maxGifWidth, int maxGifHeight, int lzwMaxBits>
void GifDecoder<maxGifWidth, maxGifHeight, lzwMaxBits>::lzw_setTempBuffer(uint8_t * tempBuffer) {
temp_buffer = tempBuffer;
}
// Initialize LZW decoder
// csize initial code size in bits
// buf input data
template <int maxGifWidth, int maxGifHeight, int lzwMaxBits>
void GifDecoder<maxGifWidth, maxGifHeight, lzwMaxBits>::lzw_decode_init (int csize) {
// Initialize read buffer variables
bbuf = 0;
bbits = 0;
bs = 0;
bcnt = 0;
// Initialize decoder variables
codesize = csize;
cursize = codesize + 1;
curmask = mask[cursize];
top_slot = 1 << cursize;
clear_code = 1 << codesize;
end_code = clear_code + 1;
slot = newcodes = clear_code + 2;
oc = fc = -1;
sp = stack;
}
// Get one code of given number of bits from stream
template <int maxGifWidth, int maxGifHeight, int lzwMaxBits>
int GifDecoder<maxGifWidth, maxGifHeight, lzwMaxBits>::lzw_get_code() {
while (bbits < cursize) {
if (bcnt == bs) {
// get number of bytes in next block
readIntoBuffer(temp_buffer, 1);
bs = temp_buffer[0];
readIntoBuffer(temp_buffer, bs);
bcnt = 0;
}
bbuf |= temp_buffer[bcnt] << bbits;
bbits += 8;
bcnt++;
}
int c = bbuf;
bbuf >>= cursize;
bbits -= cursize;
return c & curmask;
}
// Decode given number of bytes
// buf 8 bit output buffer
// len number of pixels to decode
// returns the number of bytes decoded
template <int maxGifWidth, int maxGifHeight, int lzwMaxBits>
int GifDecoder<maxGifWidth, maxGifHeight, lzwMaxBits>::lzw_decode(uint8_t *buf, int len, uint8_t *bufend) {
int l, c, code;
#if LZWDEBUG == 1
unsigned char debugMessagePrinted = 0;
#endif
if (end_code < 0) {
return 0;
}
l = len;
for (;;) {
while (sp > stack) {
// load buf with data if we're still within bounds
if(buf < bufend) {
*buf++ = *(--sp);
} else {
// out of bounds, keep incrementing the pointers, but don't use the data
#if LZWDEBUG == 1
// only print this message once per call to lzw_decode
if(buf == bufend)
Serial.println("****** LZW imageData buffer overrun *******");
#endif
}
if ((--l) == 0) {
return len;
}
}
c = lzw_get_code();
if (c == end_code) {
break;
}
else if (c == clear_code) {
cursize = codesize + 1;
curmask = mask[cursize];
slot = newcodes;
top_slot = 1 << cursize;
fc= oc= -1;
}
else {
code = c;
if ((code == slot) && (fc >= 0)) {
*sp++ = fc;
code = oc;
}
else if (code >= slot) {
break;
}
while (code >= newcodes) {
*sp++ = suffix[code];
code = prefix[code];
}
*sp++ = code;
if ((slot < top_slot) && (oc >= 0)) {
suffix[slot] = code;
prefix[slot++] = oc;
}
fc = code;
oc = c;
if (slot >= top_slot) {
if (cursize < lzwMaxBits) {
top_slot <<= 1;
curmask = mask[++cursize];
} else {
#if LZWDEBUG == 1
if(!debugMessagePrinted) {
debugMessagePrinted = 1;
Serial.println("****** cursize >= lzwMaxBits *******");
}
#endif
}
}
}
}
end_code = -1;
return len - l;
}

View file

@ -0,0 +1 @@
https://loading.io/animation/text/

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB