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 |