Updated GIF example
|
@ -1,136 +0,0 @@
|
||||||
/*
|
|
||||||
* 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"
|
|
||||||
|
|
||||||
/* GIF files for this particular example need to be put into the 'data' directoy of the
|
|
||||||
* sketch and saved to the ESP32 using the "ESP32 Sketch Data Upload" tool in Arduino.
|
|
||||||
*
|
|
||||||
* URL: https://github.com/me-no-dev/arduino-esp32fs-plugin
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
#define GIF_DIRECTORY "/"
|
|
||||||
#define DISPLAY_TIME_SECONDS 5
|
|
||||||
|
|
||||||
// Gif sizes should match exactly that of the RGB Matrix display.
|
|
||||||
const uint8_t GIFWidth = 64;
|
|
||||||
const uint8_t GIFHeight = 32;
|
|
||||||
|
|
||||||
/* 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<GIFWidth, GIFHeight, 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.drawPixelRGB888(x, y, 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();
|
|
||||||
}
|
|
|
@ -1,179 +0,0 @@
|
||||||
// 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);
|
|
||||||
}
|
|
|
@ -1,169 +0,0 @@
|
||||||
/*
|
|
||||||
* 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);
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
#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
|
|
|
@ -1,148 +0,0 @@
|
||||||
#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
|
|
|
@ -1,806 +0,0 @@
|
||||||
/*
|
|
||||||
* 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)();
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
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.
|
|
|
@ -1,169 +0,0 @@
|
||||||
/*
|
|
||||||
* 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 +0,0 @@
|
||||||
https://loading.io/animation/text/
|
|
Before Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 171 KiB After Width: | Height: | Size: 171 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |