Animated GIF example added
This commit is contained in:
parent
6836c4a563
commit
528c8ed561
13 changed files with 1811 additions and 13 deletions
|
@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
127
examples/AnimatedGIF/AnimatedGIF.ino
Normal file
127
examples/AnimatedGIF/AnimatedGIF.ino
Normal 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();
|
||||
}
|
179
examples/AnimatedGIF/FSBrowser.h
Normal file
179
examples/AnimatedGIF/FSBrowser.h
Normal 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);
|
||||
}
|
169
examples/AnimatedGIF/FilenameFunctions.cpp
Normal file
169
examples/AnimatedGIF/FilenameFunctions.cpp
Normal 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);
|
||||
}
|
21
examples/AnimatedGIF/FilenameFunctions.h
Normal file
21
examples/AnimatedGIF/FilenameFunctions.h
Normal 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
|
148
examples/AnimatedGIF/GifDecoder.h
Normal file
148
examples/AnimatedGIF/GifDecoder.h
Normal 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
|
806
examples/AnimatedGIF/GifDecoder_Impl.h
Normal file
806
examples/AnimatedGIF/GifDecoder_Impl.h
Normal 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)();
|
||||
}
|
21
examples/AnimatedGIF/LICENSE.txt
Normal file
21
examples/AnimatedGIF/LICENSE.txt
Normal 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.
|
169
examples/AnimatedGIF/LzwDecoder_Impl.h
Normal file
169
examples/AnimatedGIF/LzwDecoder_Impl.h
Normal 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;
|
||||
}
|
1
examples/AnimatedGIF/data/Text_Animations.txt.txt
Normal file
1
examples/AnimatedGIF/data/Text_Animations.txt.txt
Normal file
|
@ -0,0 +1 @@
|
|||
https://loading.io/animation/text/
|
BIN
examples/AnimatedGIF/data/ezgif.com-pacmn.gif
Normal file
BIN
examples/AnimatedGIF/data/ezgif.com-pacmn.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 44 KiB |
BIN
examples/AnimatedGIF/data/loading.io-64x32px.gif
Normal file
BIN
examples/AnimatedGIF/data/loading.io-64x32px.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 39 KiB |
Loading…
Reference in a new issue