#ifndef GifPlayer_H #define GifPlayer_H #include 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(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