aboutsummaryrefslogtreecommitdiff
path: root/examples
diff options
context:
space:
mode:
authormrfaptastic <gmail@qwe23.com>2018-10-25 23:49:41 +0100
committermrfaptastic <gmail@qwe23.com>2018-10-25 23:49:41 +0100
commit528c8ed5617e6c42ffd6dbc4d7e1cf464524034f (patch)
treeb8f7b0fab47e0870f005433986649eee160641e8 /examples
parent6836c4a5635f629faf3b5c4cc7d0ae25887fef3b (diff)
Animated GIF example added
Diffstat (limited to 'examples')
-rw-r--r--examples/AnimatedGIF/AnimatedGIF.ino127
-rw-r--r--examples/AnimatedGIF/FSBrowser.h179
-rw-r--r--examples/AnimatedGIF/FilenameFunctions.cpp169
-rw-r--r--examples/AnimatedGIF/FilenameFunctions.h21
-rw-r--r--examples/AnimatedGIF/GifDecoder.h148
-rw-r--r--examples/AnimatedGIF/GifDecoder_Impl.h806
-rw-r--r--examples/AnimatedGIF/LICENSE.txt21
-rw-r--r--examples/AnimatedGIF/LzwDecoder_Impl.h169
-rw-r--r--examples/AnimatedGIF/data/Text_Animations.txt.txt1
-rw-r--r--examples/AnimatedGIF/data/ezgif.com-pacmn.gifbin0 -> 44831 bytes
-rw-r--r--examples/AnimatedGIF/data/loading.io-64x32px.gifbin0 -> 39645 bytes
11 files changed, 1641 insertions, 0 deletions
diff --git a/examples/AnimatedGIF/AnimatedGIF.ino b/examples/AnimatedGIF/AnimatedGIF.ino
new file mode 100644
index 0000000..856199c
--- /dev/null
+++ b/examples/AnimatedGIF/AnimatedGIF.ino
@@ -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();
+}
diff --git a/examples/AnimatedGIF/FSBrowser.h b/examples/AnimatedGIF/FSBrowser.h
new file mode 100644
index 0000000..c380226
--- /dev/null
+++ b/examples/AnimatedGIF/FSBrowser.h
@@ -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);
+}
diff --git a/examples/AnimatedGIF/FilenameFunctions.cpp b/examples/AnimatedGIF/FilenameFunctions.cpp
new file mode 100644
index 0000000..e8a8f04
--- /dev/null
+++ b/examples/AnimatedGIF/FilenameFunctions.cpp
@@ -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);
+}
diff --git a/examples/AnimatedGIF/FilenameFunctions.h b/examples/AnimatedGIF/FilenameFunctions.h
new file mode 100644
index 0000000..2c92c10
--- /dev/null
+++ b/examples/AnimatedGIF/FilenameFunctions.h
@@ -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
diff --git a/examples/AnimatedGIF/GifDecoder.h b/examples/AnimatedGIF/GifDecoder.h
new file mode 100644
index 0000000..8a3d14a
--- /dev/null
+++ b/examples/AnimatedGIF/GifDecoder.h
@@ -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
diff --git a/examples/AnimatedGIF/GifDecoder_Impl.h b/examples/AnimatedGIF/GifDecoder_Impl.h
new file mode 100644
index 0000000..c7c5a5d
--- /dev/null
+++ b/examples/AnimatedGIF/GifDecoder_Impl.h
@@ -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)();
+}
diff --git a/examples/AnimatedGIF/LICENSE.txt b/examples/AnimatedGIF/LICENSE.txt
new file mode 100644
index 0000000..c69f703
--- /dev/null
+++ b/examples/AnimatedGIF/LICENSE.txt
@@ -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.
diff --git a/examples/AnimatedGIF/LzwDecoder_Impl.h b/examples/AnimatedGIF/LzwDecoder_Impl.h
new file mode 100644
index 0000000..720ac24
--- /dev/null
+++ b/examples/AnimatedGIF/LzwDecoder_Impl.h
@@ -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;
+}
diff --git a/examples/AnimatedGIF/data/Text_Animations.txt.txt b/examples/AnimatedGIF/data/Text_Animations.txt.txt
new file mode 100644
index 0000000..8431cd4
--- /dev/null
+++ b/examples/AnimatedGIF/data/Text_Animations.txt.txt
@@ -0,0 +1 @@
+https://loading.io/animation/text/ \ No newline at end of file
diff --git a/examples/AnimatedGIF/data/ezgif.com-pacmn.gif b/examples/AnimatedGIF/data/ezgif.com-pacmn.gif
new file mode 100644
index 0000000..0a219a4
--- /dev/null
+++ b/examples/AnimatedGIF/data/ezgif.com-pacmn.gif
Binary files differ
diff --git a/examples/AnimatedGIF/data/loading.io-64x32px.gif b/examples/AnimatedGIF/data/loading.io-64x32px.gif
new file mode 100644
index 0000000..342f8ae
--- /dev/null
+++ b/examples/AnimatedGIF/data/loading.io-64x32px.gif
Binary files differ