Files
awtrix-light/lib/MatrixUI/GifPlayer.h
Stephan Mühl 2a8149400b update
2023-04-09 19:29:08 +02:00

661 lines
14 KiB
C++

#ifndef GifPlayer_H
#define GifPlayer_H
#include <LittleFS.h>
class GifPlayer
{
public:
#define ERROR_NONE 0
#define ERROR_FILEOPEN 1
#define ERROR_FILENOTGIF 2
#define ERROR_BADGIFFORMAT 3
#define ERROR_UNKNOWNCONTROLEXT 4
#define ERROR_FINISHED 5
#define WIDTH 32
#define HEIGHT 8
private:
bool needNewFrame;
long lastFrameTime;
bool firstFrameDone;
int newframeDelay;
int lastFrame[WIDTH * HEIGHT];
bool lastFrameDrawn = false;
unsigned long nextFrameTime = 0;
#define GIFHDRTAGNORM "GIF87a"
#define GIFHDRTAGNORM1 "GIF89a"
#define GIFHDRSIZE 6
FastLED_NeoMatrix *mtx;
#define COLORTBLFLAG 0x80
#define INTERLACEFLAG 0x40
#define TRANSPARENTFLAG 0x01
#define NO_TRANSPARENT_INDEX -1
#define DISPOSAL_NONE 0
#define DISPOSAL_LEAVE 1
#define DISPOSAL_BACKGROUND 2
#define DISPOSAL_RESTORE 3
typedef struct
{
byte Red;
byte Green;
byte Blue;
} _RGB;
int lsdWidth;
int lsdHeight;
int lsdPackedField;
int lsdAspectRatio;
int lsdBackgroundIndex;
int offsetX;
int offsetY;
int tbiImageX;
int tbiImageY;
int tbiWidth;
int tbiHeight;
int tbiPackedBits;
boolean tbiInterlaced;
public:
int frameDelay;
int transparentColorIndex;
int prevBackgroundIndex;
int prevDisposalMethod;
int disposalMethod;
int lzwCodeSize;
boolean keyFrame;
int rectX;
int rectY;
int rectWidth;
int rectHeight;
int colorCount;
_RGB gifPalette[256];
byte lzwImageData[1280];
char tempBuffer[260];
File file;
byte imageData[WIDTH * HEIGHT];
byte imageDataBU[WIDTH * HEIGHT];
void backUpStream(int n)
{
file.seek(file.position() - n, SeekSet);
}
int readByte()
{
int b = file.read();
return b;
}
int readWord()
{
int b0 = readByte();
int b1 = readByte();
return (b1 << 8) | b0;
}
int readIntoBuffer(void *buffer, int numberOfBytes)
{
int result = file.read(static_cast<uint8_t *>(buffer), numberOfBytes);
return result;
}
void fillImageDataRect(byte colorIndex, int x, int y, int width, int height)
{
int yOffset;
for (int yy = y; yy < height + y; yy++)
{
yOffset = yy * WIDTH;
for (int xx = x; xx < width + x; xx++)
{
imageData[yOffset + xx] = colorIndex;
}
}
}
void fillImageData(byte colorIndex)
{
memset(imageData, colorIndex, sizeof(imageData));
}
void copyImageDataRect(byte *src, byte *dst, int x, int y, int width, int height)
{
int yOffset, offset;
for (int yy = y; yy < height + y; yy++)
{
yOffset = yy * WIDTH;
for (int xx = x; xx < width + x; xx++)
{
offset = yOffset + xx;
dst[offset] = src[offset];
}
}
}
void parsePlainTextExtension()
{
byte len = readByte();
readIntoBuffer(tempBuffer, len);
len = readByte();
while (len != 0)
{
readIntoBuffer(tempBuffer, len);
len = readByte();
}
}
void parseGraphicControlExtension()
{
readByte();
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;
}
readByte(); // Toss block end
}
void parseApplicationExtension()
{
memset(tempBuffer, 0, sizeof(tempBuffer));
byte len = readByte();
readIntoBuffer(tempBuffer, len);
len = readByte();
while (len != 0)
{
readIntoBuffer(tempBuffer, len);
len = readByte();
}
}
void parseCommentExtension()
{
byte len = readByte();
while (len != 0)
{
memset(tempBuffer, 0, sizeof(tempBuffer));
readIntoBuffer(tempBuffer, len);
len = readByte();
}
}
int parseGIFFileTerminator()
{
byte b = readByte();
if (b != 0x3B)
{
return ERROR_BADGIFFORMAT;
}
else
{
return ERROR_NONE;
}
}
unsigned long parseTableBasedImage()
{
tbiImageX = readWord();
tbiImageY = readWord();
tbiWidth = readWord();
tbiHeight = readWord();
tbiPackedBits = readByte();
tbiInterlaced = ((tbiPackedBits & INTERLACEFLAG) != 0);
boolean localColorTable = ((tbiPackedBits & COLORTBLFLAG) != 0);
if (localColorTable)
{
int colorBits = ((tbiPackedBits & 7) + 1);
colorCount = 1 << colorBits;
int colorTableBytes = sizeof(_RGB) * colorCount;
readIntoBuffer(gifPalette, colorTableBytes);
}
if (keyFrame)
{
if (transparentColorIndex == NO_TRANSPARENT_INDEX)
{
fillImageData(lsdBackgroundIndex);
}
else
{
fillImageData(transparentColorIndex);
}
keyFrame = false;
rectX = 0;
rectY = 0;
rectWidth = WIDTH;
rectHeight = HEIGHT;
}
if ((prevDisposalMethod != DISPOSAL_NONE) && (prevDisposalMethod != DISPOSAL_LEAVE))
{
// mtx->clear();
}
if (prevDisposalMethod == DISPOSAL_BACKGROUND)
{
fillImageDataRect(prevBackgroundIndex, rectX, rectY, rectWidth, rectHeight);
}
else if (prevDisposalMethod == DISPOSAL_RESTORE)
{
copyImageDataRect(imageDataBU, imageData, rectX, rectY, rectWidth, rectHeight);
}
prevDisposalMethod = disposalMethod;
if (disposalMethod != DISPOSAL_NONE)
{
rectX = tbiImageX;
rectY = tbiImageY;
rectWidth = tbiWidth;
rectHeight = tbiHeight;
if (disposalMethod == DISPOSAL_BACKGROUND)
{
if (transparentColorIndex != NO_TRANSPARENT_INDEX)
{
prevBackgroundIndex = transparentColorIndex;
}
else
{
prevBackgroundIndex = lsdBackgroundIndex;
}
}
else if (disposalMethod == DISPOSAL_RESTORE)
{
copyImageDataRect(imageData, imageDataBU, rectX, rectY, rectWidth, rectHeight);
}
}
lzwCodeSize = readByte();
int offset = 0;
int dataBlockSize = readByte();
while (dataBlockSize != 0)
{
backUpStream(1);
dataBlockSize++;
if (offset + dataBlockSize <= (int)sizeof(lzwImageData))
{
readIntoBuffer(lzwImageData + offset, dataBlockSize);
}
else
{
int i;
for (i = 0; i < dataBlockSize; i++)
file.read();
}
offset += dataBlockSize;
dataBlockSize = readByte();
}
lzw_decode_init(lzwCodeSize, lzwImageData);
decompressAndDisplayFrame();
transparentColorIndex = NO_TRANSPARENT_INDEX;
disposalMethod = DISPOSAL_NONE;
if (frameDelay < 1)
{
frameDelay = 1;
}
newframeDelay = frameDelay * 10;
return frameDelay * 10;
}
#define LZW_MAXBITS 10
#define LZW_SIZTABLE (1 << LZW_MAXBITS)
unsigned int mask[17] = {
0x0000, 0x0001, 0x0003, 0x0007,
0x000F, 0x001F, 0x003F, 0x007F,
0x00FF, 0x01FF, 0x03FF, 0x07FF,
0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF,
0xFFFF};
byte *pbuf;
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
byte *sp;
byte stack[LZW_SIZTABLE];
byte suffix[LZW_SIZTABLE];
unsigned int prefix[LZW_SIZTABLE];
void lzw_decode_init(int csize, byte *buf)
{
pbuf = buf;
bbuf = 0;
bbits = 0;
bs = 0;
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;
}
int lzw_get_code()
{
while (bbits < cursize)
{
if (!bs)
{
bs = *pbuf++;
}
bbuf |= (*pbuf++) << bbits;
bbits += 8;
bs--;
}
int c = bbuf;
bbuf >>= cursize;
bbits -= cursize;
return c & curmask;
}
int lzw_decode(byte *buf, int len)
{
int l, c, code;
if (end_code < 0)
{
return 0;
}
l = len;
for (;;)
{
while (sp > stack)
{
*buf++ = *(--sp);
if ((--l) == 0)
{
goto the_end;
}
}
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 < LZW_MAXBITS)
{
top_slot <<= 1;
curmask = mask[++cursize];
}
else
{
}
}
}
}
end_code = -1;
the_end:
return len - l;
}
void redrawLastFrame()
{
if (needNewFrame)
return;
CRGB color;
int yOffset, pixel;
for (int y = tbiImageY; y < tbiHeight + tbiImageY; y++)
{
yOffset = y * WIDTH;
for (int x = tbiImageX; x < tbiWidth + tbiImageX; x++)
{
pixel = lastFrame[yOffset + x];
if (pixel == -99)
{
mtx->drawPixel(x + offsetX, y + offsetY, mtx->Color(0, 0, 0));
continue;
}
color.red = gifPalette[pixel].Red;
color.green = gifPalette[pixel].Green;
color.blue = gifPalette[pixel].Blue;
mtx->drawPixel(x + offsetX, y + offsetY, color);
}
}
}
void decompressAndDisplayFrame()
{
CRGB color;
if (tbiInterlaced)
{
for (int line = tbiImageY + 0; line < tbiHeight + tbiImageY; line += 8)
{
lzw_decode(imageData + (line * WIDTH) + tbiImageX, tbiWidth);
}
for (int line = tbiImageY + 4; line < tbiHeight + tbiImageY; line += 8)
{
lzw_decode(imageData + (line * WIDTH) + tbiImageX, tbiWidth);
}
for (int line = tbiImageY + 2; line < tbiHeight + tbiImageY; line += 4)
{
lzw_decode(imageData + (line * WIDTH) + tbiImageX, tbiWidth);
}
for (int line = tbiImageY + 1; line < tbiHeight + tbiImageY; line += 2)
{
lzw_decode(imageData + (line * WIDTH) + tbiImageX, tbiWidth);
}
}
else
{
for (int line = tbiImageY; line < tbiHeight + tbiImageY; line++)
{
lzw_decode(imageData + (line * WIDTH) + tbiImageX, tbiWidth);
}
}
// Ersetze alle transparenten Pixel durch schwarze Pixel
for (int i = 0; i < tbiWidth * tbiHeight; i++)
{
if (imageData[i] == transparentColorIndex)
{
imageData[i] = -99; // Indexwert für Schwarz
}
}
int pixel, yOffset;
for (int y = tbiImageY; y < tbiHeight + tbiImageY; y++)
{
yOffset = y * WIDTH;
for (int x = tbiImageX; x < tbiWidth + tbiImageX; x++)
{
pixel = imageData[yOffset + x];
if (pixel == transparentColorIndex) // Check if the pixel index is the transparent index
{
mtx->drawPixel(x + offsetX, y + offsetY, mtx->Color(0, 0, 0)); // Draw a black pixel
lastFrame[yOffset + x] = -99; // Save it as a special value
}
else
{
color.red = gifPalette[pixel].Red;
color.green = gifPalette[pixel].Green;
color.blue = gifPalette[pixel].Blue;
mtx->drawPixel(x + offsetX, y + offsetY, color);
lastFrame[yOffset + x] = pixel;
}
}
}
needNewFrame = false;
lastFrameTime = millis();
}
public:
void setMatrix(FastLED_NeoMatrix *matrix)
{
mtx = matrix;
}
void playGif(int x, int y, File *imageFile)
{
offsetX = x;
offsetY = y;
if (imageFile->name() == file.name())
{
drawFrame();
return;
}
else
{
needNewFrame = true;
file = *imageFile;
memset(lastFrame, 0, sizeof(lastFrame));
memset(gifPalette, 0, sizeof(gifPalette));
memset(lzwImageData, 0, sizeof(lzwImageData));
memset(imageData, 0, sizeof(imageData));
memset(imageDataBU, 0, sizeof(imageDataBU));
memset(stack, 0, sizeof(stack));
memset(suffix, 0, sizeof(suffix));
memset(prefix, 0, sizeof(prefix));
parseGifHeader();
parseLogicalScreenDescriptor();
parseGlobalColorTable();
drawFrame();
}
}
boolean parseGifHeader()
{
char buffer[10];
readIntoBuffer(buffer, GIFHDRSIZE);
if ((strncmp(buffer, GIFHDRTAGNORM, GIFHDRSIZE) != 0) &&
(strncmp(buffer, GIFHDRTAGNORM1, GIFHDRSIZE) != 0))
{
return false;
}
else
{
return true;
}
}
void parseLogicalScreenDescriptor()
{
lsdWidth = readWord();
lsdHeight = readWord();
lsdPackedField = readByte();
lsdBackgroundIndex = readByte();
lsdAspectRatio = readByte();
}
void parseGlobalColorTable()
{
if (lsdPackedField & COLORTBLFLAG)
{
colorCount = 1 << ((lsdPackedField & 7) + 1);
int colorTableBytes = sizeof(_RGB) * colorCount;
readIntoBuffer(gifPalette, colorTableBytes);
}
}
unsigned long drawFrame()
{
if (millis() - lastFrameTime < newframeDelay)
{
redrawLastFrame();
return 0;
}
lastFrameDrawn = false;
boolean done = false;
while (!done)
{
byte b = readByte();
if (b == 0x2c)
{
parseTableBasedImage();
return 0;
}
else if (b == 0x21)
{
b = readByte();
switch (b)
{
case 0x01:
parsePlainTextExtension();
break;
case 0xf9:
parseGraphicControlExtension();
break;
case 0xfe:
parseCommentExtension();
break;
case 0xff:
parseApplicationExtension();
break;
default:
return ERROR_UNKNOWNCONTROLEXT;
}
}
else
{
done = true;
file.seek(0);
parseGifHeader();
parseLogicalScreenDescriptor();
parseGlobalColorTable();
drawFrame();
return ERROR_FINISHED;
}
}
return ERROR_NONE;
}
};
#endif