├── .github └── FUNDING.yml ├── .gitignore ├── AnimatedGIF.cpp ├── AnimatedGIF.h ├── ESP32ManyRoundScreenTest.ino ├── GIFDraw.ino ├── LICENSE ├── README.md ├── gif.h └── images ├── Screens-Test.png ├── bb8.h ├── darthvader.h ├── hud_1.h ├── hud_2.h ├── hud_5.h ├── hud_6.h ├── hud_7.h ├── hyperspace.h ├── nostromo.h └── x_wing.h /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | custom: https://www.buymeacoffee.com/thelastoutpostworkshop 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | .vscode 3 | AnimatedGIF.cpp 4 | AnimatedGIF.h -------------------------------------------------------------------------------- /AnimatedGIF.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // GIF Animator 3 | // written by Larry Bank 4 | // bitbank@pobox.com 5 | // Arduino port started 7/5/2020 6 | // Original GIF code written 20+ years ago :) 7 | // The goal of this code is to decode images up to 480x320 8 | // using no more than 22K of RAM (if sent directly to an LCD display) 9 | // 10 | // Copyright 2020 BitBank Software, Inc. All Rights Reserved. 11 | // Licensed under the Apache License, Version 2.0 (the "License"); 12 | // you may not use this file except in compliance with the License. 13 | // You may obtain a copy of the License at 14 | // http://www.apache.org/licenses/LICENSE-2.0 15 | // Unless required by applicable law or agreed to in writing, software 16 | // distributed under the License is distributed on an "AS IS" BASIS, 17 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | // See the License for the specific language governing permissions and 19 | // limitations under the License. 20 | //=========================================================================== 21 | #include "AnimatedGIF.h" 22 | 23 | // Here is all of the actual code... 24 | #include "gif.h" 25 | 26 | // 27 | // Memory initialization 28 | // 29 | int AnimatedGIF::open(uint8_t *pData, int iDataSize, GIF_DRAW_CALLBACK *pfnDraw) 30 | { 31 | _gif.iError = GIF_SUCCESS; 32 | _gif.pfnRead = readMem; 33 | _gif.pfnSeek = seekMem; 34 | _gif.pfnDraw = pfnDraw; 35 | _gif.pfnOpen = NULL; 36 | _gif.pfnClose = NULL; 37 | _gif.GIFFile.iSize = iDataSize; 38 | _gif.GIFFile.pData = pData; 39 | 40 | // Modified from the original library to use RAM instead of static memory 41 | _gif.usGIFTable = (unsigned short *)malloc(4096 * sizeof(unsigned short)); 42 | if (_gif.usGIFTable == NULL) 43 | { 44 | // Handle memory allocation failure 45 | Serial.printf("Handle memory allocation failure \n"); 46 | 47 | return GIF_ERROR_MEMORY; // or an appropriate error code 48 | } 49 | 50 | return GIFInit(&_gif); 51 | } /* open() */ 52 | 53 | int AnimatedGIF::openFLASH(uint8_t *pData, int iDataSize, GIF_DRAW_CALLBACK *pfnDraw) 54 | { 55 | _gif.iError = GIF_SUCCESS; 56 | _gif.pfnRead = readFLASH; 57 | _gif.pfnSeek = seekMem; 58 | _gif.pfnDraw = pfnDraw; 59 | _gif.pfnOpen = NULL; 60 | _gif.pfnClose = NULL; 61 | _gif.GIFFile.iSize = iDataSize; 62 | _gif.GIFFile.pData = pData; 63 | return GIFInit(&_gif); 64 | } /* openFLASH() */ 65 | 66 | // 67 | // Returns the first comment block found (if any) 68 | // 69 | int AnimatedGIF::getComment(char *pDest) 70 | { 71 | int32_t iOldPos; 72 | 73 | iOldPos = _gif.GIFFile.iPos; // keep old position 74 | (*_gif.pfnSeek)(&_gif.GIFFile, _gif.iCommentPos); 75 | (*_gif.pfnRead)(&_gif.GIFFile, (uint8_t *)pDest, _gif.sCommentLen); 76 | (*_gif.pfnSeek)(&_gif.GIFFile, iOldPos); 77 | pDest[_gif.sCommentLen] = 0; // zero terminate the string 78 | return (int)_gif.sCommentLen; 79 | } /* getComment() */ 80 | 81 | // 82 | // Allocate a block of memory to hold the entire canvas (as 8-bpp) 83 | // 84 | int AnimatedGIF::allocFrameBuf(GIF_ALLOC_CALLBACK *pfnAlloc) 85 | { 86 | if (_gif.iCanvasWidth > 0 && _gif.iCanvasHeight > 0 && _gif.pFrameBuffer == NULL) 87 | { 88 | // Allocate a little extra space for the current line 89 | // as RGB565 or RGB888 90 | int iCanvasSize = _gif.iCanvasWidth * (_gif.iCanvasHeight + 3); 91 | _gif.pFrameBuffer = (unsigned char *)(*pfnAlloc)(iCanvasSize); 92 | if (_gif.pFrameBuffer == NULL) 93 | return GIF_ERROR_MEMORY; 94 | return GIF_SUCCESS; 95 | } 96 | return GIF_INVALID_PARAMETER; 97 | } /* allocFrameBuf() */ 98 | // 99 | // Set the DRAW callback behavior to RAW (default) 100 | // or COOKED (requires allocating a frame buffer) 101 | // 102 | int AnimatedGIF::setDrawType(int iType) 103 | { 104 | if (iType != GIF_DRAW_RAW && iType != GIF_DRAW_COOKED) 105 | return GIF_INVALID_PARAMETER; // invalid drawing mode 106 | _gif.ucDrawType = (uint8_t)iType; 107 | return GIF_SUCCESS; 108 | } /* setDrawType() */ 109 | // 110 | // Release the memory used by the frame buffer 111 | // 112 | int AnimatedGIF::freeFrameBuf(GIF_FREE_CALLBACK *pfnFree) 113 | { 114 | if (_gif.pFrameBuffer) 115 | { 116 | (*pfnFree)(_gif.pFrameBuffer); 117 | _gif.pFrameBuffer = NULL; 118 | return GIF_SUCCESS; 119 | } 120 | return GIF_INVALID_PARAMETER; 121 | } /* freeFrameBuf() */ 122 | // 123 | // Return a pointer to the frame buffer (if it was allocated) 124 | // 125 | uint8_t *AnimatedGIF::getFrameBuf() 126 | { 127 | return _gif.pFrameBuffer; 128 | } /* getFrameBuf() */ 129 | 130 | int AnimatedGIF::getCanvasWidth() 131 | { 132 | return _gif.iCanvasWidth; 133 | } /* getCanvasWidth() */ 134 | 135 | int AnimatedGIF::getCanvasHeight() 136 | { 137 | return _gif.iCanvasHeight; 138 | } /* getCanvasHeight() */ 139 | 140 | int AnimatedGIF::getInfo(GIFINFO *pInfo) 141 | { 142 | return GIF_getInfo(&_gif, pInfo); 143 | } /* getInfo() */ 144 | 145 | int AnimatedGIF::getLastError() 146 | { 147 | return _gif.iError; 148 | } /* getLastError() */ 149 | 150 | // 151 | // File (SD/MMC) based initialization 152 | // 153 | int AnimatedGIF::open(const char *szFilename, GIF_OPEN_CALLBACK *pfnOpen, GIF_CLOSE_CALLBACK *pfnClose, GIF_READ_CALLBACK *pfnRead, GIF_SEEK_CALLBACK *pfnSeek, GIF_DRAW_CALLBACK *pfnDraw) 154 | { 155 | _gif.iError = GIF_SUCCESS; 156 | _gif.pfnRead = pfnRead; 157 | _gif.pfnSeek = pfnSeek; 158 | _gif.pfnDraw = pfnDraw; 159 | _gif.pfnOpen = pfnOpen; 160 | _gif.pfnClose = pfnClose; 161 | _gif.GIFFile.fHandle = (*pfnOpen)(szFilename, &_gif.GIFFile.iSize); 162 | if (_gif.GIFFile.fHandle == NULL) 163 | { 164 | _gif.iError = GIF_FILE_NOT_OPEN; 165 | return 0; 166 | } 167 | return GIFInit(&_gif); 168 | 169 | } /* open() */ 170 | 171 | void AnimatedGIF::close() 172 | { 173 | if (_gif.pfnClose) 174 | (*_gif.pfnClose)(_gif.GIFFile.fHandle); 175 | 176 | // Modified from the original library 177 | free(_gif.usGIFTable); 178 | _gif.usGIFTable = NULL; 179 | } /* close() */ 180 | 181 | void AnimatedGIF::reset() 182 | { 183 | (*_gif.pfnSeek)(&_gif.GIFFile, 0); 184 | } /* reset() */ 185 | 186 | void AnimatedGIF::begin(unsigned char ucPaletteType) 187 | { 188 | memset(&_gif, 0, sizeof(_gif)); 189 | if (ucPaletteType != GIF_PALETTE_RGB565_LE && ucPaletteType != GIF_PALETTE_RGB565_BE && ucPaletteType != GIF_PALETTE_RGB888) 190 | _gif.iError = GIF_INVALID_PARAMETER; 191 | _gif.ucPaletteType = ucPaletteType; 192 | _gif.ucDrawType = GIF_DRAW_RAW; // assume RAW pixel handling 193 | _gif.pFrameBuffer = NULL; 194 | } /* begin() */ 195 | // 196 | // Play a single frame 197 | // returns: 198 | // 1 = good result and more frames exist 199 | // 0 = no more frames exist, a frame may or may not have been played: use getLastError() and look for GIF_SUCCESS to know if a frame was played 200 | // -1 = error 201 | int AnimatedGIF::playFrame(bool bSync, int *delayMilliseconds, void *pUser) 202 | { 203 | int rc; 204 | #if !defined(__MACH__) && !defined(__LINUX__) 205 | long lTime = millis(); 206 | #endif 207 | 208 | if (_gif.GIFFile.iPos >= _gif.GIFFile.iSize - 1) // no more data exists 209 | { 210 | (*_gif.pfnSeek)(&_gif.GIFFile, 0); // seek to start 211 | } 212 | if (GIFParseInfo(&_gif, 0)) 213 | { 214 | _gif.pUser = pUser; 215 | if (_gif.iError == GIF_EMPTY_FRAME) // don't try to decode it 216 | return 0; 217 | rc = DecodeLZW(&_gif, 0); 218 | if (rc != 0) // problem 219 | return -1; 220 | } 221 | else 222 | { 223 | // The file is "malformed" in that there is a bunch of non-image data after 224 | // the last frame. Return as if all is well, though if needed getLastError() 225 | // can be used to see if a frame was actually processed: 226 | // GIF_SUCCESS -> frame processed, GIF_EMPTY_FRAME -> no frame processed 227 | if (_gif.iError == GIF_EMPTY_FRAME) 228 | { 229 | if (delayMilliseconds) 230 | *delayMilliseconds = 0; 231 | return 0; 232 | } 233 | return -1; // error parsing the frame info, we may be at the end of the file 234 | } 235 | // Return 1 for more frames or 0 if this was the last frame 236 | if (bSync) 237 | { 238 | #if !defined(__MACH__) && !defined(__LINUX__) 239 | lTime = millis() - lTime; 240 | if (lTime < _gif.iFrameDelay) // need to pause a bit 241 | delay(_gif.iFrameDelay - lTime); 242 | #endif // __LINUX__ 243 | } 244 | if (delayMilliseconds) // if not NULL, return the frame delay time 245 | *delayMilliseconds = _gif.iFrameDelay; 246 | return (_gif.GIFFile.iPos < _gif.GIFFile.iSize - 10); 247 | } /* playFrame() */ 248 | -------------------------------------------------------------------------------- /AnimatedGIF.h: -------------------------------------------------------------------------------- 1 | // Copyright 2020 BitBank Software, Inc. All Rights Reserved. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // Unless required by applicable law or agreed to in writing, software 7 | // distributed under the License is distributed on an "AS IS" BASIS, 8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | // See the License for the specific language governing permissions and 10 | // limitations under the License. 11 | //=========================================================================== 12 | 13 | #ifndef __ANIMATEDGIF__ 14 | #define __ANIMATEDGIF__ 15 | #if defined( PICO_BUILD ) || defined( __MACH__ ) || defined( __LINUX__ ) || defined( __MCUXPRESSO ) 16 | #include 17 | #include 18 | #include 19 | #include 20 | #define memcpy_P memcpy 21 | #define PROGMEM 22 | #else 23 | #include 24 | #endif 25 | // 26 | // GIF Animator 27 | // Written by Larry Bank 28 | // Copyright (c) 2020 BitBank Software, Inc. 29 | // bitbank@pobox.com 30 | // 31 | // Designed to decode images up to 480x320 32 | // using less than 22K of RAM 33 | // 34 | 35 | /* GIF Defines and variables */ 36 | #define MAX_CHUNK_SIZE 255 37 | #define LZW_BUF_SIZE (6*MAX_CHUNK_SIZE) 38 | #define LZW_HIGHWATER (4*MAX_CHUNK_SIZE) 39 | #ifdef __LINUX__ 40 | #define MAX_WIDTH 2048 41 | #else 42 | #define MAX_WIDTH 240 43 | #endif // __LINUX__ 44 | #define FILE_BUF_SIZE 4096 45 | 46 | #define PIXEL_FIRST 0 47 | #define PIXEL_LAST 4096 48 | #define LINK_UNUSED 5911 // 0x1717 to use memset 49 | #define LINK_END 5912 50 | #define MAX_HASH 5003 51 | #define MAXMAXCODE 4096 52 | 53 | enum { 54 | GIF_PALETTE_RGB565_LE = 0, // little endian (default) 55 | GIF_PALETTE_RGB565_BE, // big endian 56 | GIF_PALETTE_RGB888 // original 24-bpp entries 57 | }; 58 | // for compatibility with older code 59 | #define LITTLE_ENDIAN_PIXELS GIF_PALETTE_RGB565_LE 60 | #define BIG_ENDIAN_PIXELS GIF_PALETTE_RGB565_BE 61 | // 62 | // Draw callback pixel type 63 | // RAW = 8-bit palettized pixels requiring transparent pixel handling 64 | // COOKED = 16 or 24-bpp fully rendered pixels ready for display 65 | // 66 | enum { 67 | GIF_DRAW_RAW = 0, 68 | GIF_DRAW_COOKED 69 | }; 70 | 71 | enum { 72 | GIF_SUCCESS = 0, 73 | GIF_DECODE_ERROR, 74 | GIF_TOO_WIDE, 75 | GIF_INVALID_PARAMETER, 76 | GIF_UNSUPPORTED_FEATURE, 77 | GIF_FILE_NOT_OPEN, 78 | GIF_EARLY_EOF, 79 | GIF_EMPTY_FRAME, 80 | GIF_BAD_FILE, 81 | GIF_ERROR_MEMORY 82 | }; 83 | 84 | typedef struct gif_file_tag 85 | { 86 | int32_t iPos; // current file position 87 | int32_t iSize; // file size 88 | uint8_t *pData; // memory file pointer 89 | void * fHandle; // class pointer to File/SdFat or whatever you want 90 | } GIFFILE; 91 | 92 | typedef struct gif_info_tag 93 | { 94 | int32_t iFrameCount; // total frames in file 95 | int32_t iDuration; // duration of animation in milliseconds 96 | int32_t iMaxDelay; // maximum frame delay 97 | int32_t iMinDelay; // minimum frame delay 98 | } GIFINFO; 99 | 100 | typedef struct gif_draw_tag 101 | { 102 | int iX, iY; // Corner offset of this frame on the canvas 103 | int y; // current line being drawn (0 = top line of image) 104 | int iWidth, iHeight; // size of this frame 105 | void *pUser; // user supplied pointer 106 | uint8_t *pPixels; // 8-bit source pixels for this line 107 | uint16_t *pPalette; // little or big-endian RGB565 palette entries (default) 108 | uint8_t *pPalette24; // RGB888 palette (optional) 109 | uint8_t ucTransparent; // transparent color 110 | uint8_t ucHasTransparency; // flag indicating the transparent color is in use 111 | uint8_t ucDisposalMethod; // frame disposal method 112 | uint8_t ucBackground; // background color 113 | } GIFDRAW; 114 | 115 | // Callback function prototypes 116 | typedef int32_t (GIF_READ_CALLBACK)(GIFFILE *pFile, uint8_t *pBuf, int32_t iLen); 117 | typedef int32_t (GIF_SEEK_CALLBACK)(GIFFILE *pFile, int32_t iPosition); 118 | typedef void (GIF_DRAW_CALLBACK)(GIFDRAW *pDraw); 119 | typedef void * (GIF_OPEN_CALLBACK)(const char *szFilename, int32_t *pFileSize); 120 | typedef void (GIF_CLOSE_CALLBACK)(void *pHandle); 121 | typedef void * (GIF_ALLOC_CALLBACK)(uint32_t iSize); 122 | typedef void (GIF_FREE_CALLBACK)(void *buffer); 123 | // 124 | // our private structure to hold a GIF image decode state 125 | // 126 | typedef struct gif_image_tag 127 | { 128 | int iWidth, iHeight, iCanvasWidth, iCanvasHeight; 129 | int iX, iY; // GIF corner offset 130 | int iBpp; 131 | int iError; // last error 132 | int iFrameDelay; // delay in milliseconds for this frame 133 | int iXCount, iYCount; // decoding position in image (countdown values) 134 | int iLZWOff; // current LZW data offset 135 | int iLZWSize; // current quantity of data in the LZW buffer 136 | int iCommentPos; // file offset of start of comment data 137 | short sCommentLen; // length of comment 138 | GIF_READ_CALLBACK *pfnRead; 139 | GIF_SEEK_CALLBACK *pfnSeek; 140 | GIF_DRAW_CALLBACK *pfnDraw; 141 | GIF_OPEN_CALLBACK *pfnOpen; 142 | GIF_CLOSE_CALLBACK *pfnClose; 143 | GIFFILE GIFFile; 144 | void *pUser; 145 | unsigned char *pFrameBuffer; 146 | unsigned char *pPixels, *pOldPixels; 147 | unsigned char ucLineBuf[MAX_WIDTH]; // current line 148 | unsigned char ucFileBuf[FILE_BUF_SIZE]; // holds temp data and pixel stack 149 | unsigned short pPalette[384]; // can hold RGB565 or RGB888 - set in begin() 150 | unsigned short pLocalPalette[384]; // color palettes for GIF images 151 | unsigned char ucLZW[LZW_BUF_SIZE]; // holds 6 chunks (6x255) of GIF LZW data packed together 152 | 153 | // Modified from the original library 154 | // unsigned short usGIFTable[4096]; 155 | unsigned short *usGIFTable; 156 | 157 | unsigned char ucGIFPixels[8192]; 158 | unsigned char bEndOfFrame; 159 | unsigned char ucGIFBits, ucBackground, ucTransparent, ucCodeStart, ucMap, bUseLocalPalette; 160 | unsigned char ucPaletteType; // RGB565 or RGB888 161 | unsigned char ucDrawType; // RAW or COOKED 162 | } GIFIMAGE; 163 | 164 | #ifdef __cplusplus 165 | // 166 | // The GIF class wraps portable C code which does the actual work 167 | // 168 | class AnimatedGIF 169 | { 170 | public: 171 | int open(uint8_t *pData, int iDataSize, GIF_DRAW_CALLBACK *pfnDraw); 172 | int openFLASH(uint8_t *pData, int iDataSize, GIF_DRAW_CALLBACK *pfnDraw); 173 | int open(const char *szFilename, GIF_OPEN_CALLBACK *pfnOpen, GIF_CLOSE_CALLBACK *pfnClose, GIF_READ_CALLBACK *pfnRead, GIF_SEEK_CALLBACK *pfnSeek, GIF_DRAW_CALLBACK *pfnDraw); 174 | void close(); 175 | void reset(); 176 | void begin(unsigned char ucPaletteType = GIF_PALETTE_RGB565_LE); 177 | void begin(int iEndian, unsigned char ucPaletteType) { begin(ucPaletteType); }; 178 | int playFrame(bool bSync, int *delayMilliseconds, void *pUser = NULL); 179 | int getCanvasWidth(); 180 | int allocFrameBuf(GIF_ALLOC_CALLBACK *pfnAlloc); 181 | int setDrawType(int iType); 182 | int freeFrameBuf(GIF_FREE_CALLBACK *pfnFree); 183 | uint8_t *getFrameBuf(); 184 | int getCanvasHeight(); 185 | int getInfo(GIFINFO *pInfo); 186 | int getLastError(); 187 | int getComment(char *destBuffer); 188 | 189 | private: 190 | GIFIMAGE _gif; 191 | }; 192 | #else 193 | // C interface 194 | int GIF_openRAM(GIFIMAGE *pGIF, uint8_t *pData, int iDataSize, GIF_DRAW_CALLBACK *pfnDraw); 195 | int GIF_openFile(GIFIMAGE *pGIF, const char *szFilename, GIF_DRAW_CALLBACK *pfnDraw); 196 | void GIF_close(GIFIMAGE *pGIF); 197 | void GIF_begin(GIFIMAGE *pGIF, unsigned char ucPaletteType); 198 | void GIF_reset(GIFIMAGE *pGIF); 199 | int GIF_playFrame(GIFIMAGE *pGIF, int *delayMilliseconds, void *pUser); 200 | int GIF_getCanvasWidth(GIFIMAGE *pGIF); 201 | int GIF_getCanvasHeight(GIFIMAGE *pGIF); 202 | int GIF_getComment(GIFIMAGE *pGIF, char *destBuffer); 203 | int GIF_getInfo(GIFIMAGE *pGIF, GIFINFO *pInfo); 204 | int GIF_getLastError(GIFIMAGE *pGIF); 205 | #endif // __cplusplus 206 | 207 | // Due to unaligned memory causing an exception, we have to do these macros the slow way 208 | #define INTELSHORT(p) ((*p) + (*(p+1)<<8)) 209 | #define INTELLONG(p) ((*p) + (*(p+1)<<8) + (*(p+2)<<16) + (*(p+3)<<24)) 210 | #define MOTOSHORT(p) (((*(p))<<8) + (*(p+1))) 211 | #define MOTOLONG(p) (((*p)<<24) + ((*(p+1))<<16) + ((*(p+2))<<8) + (*(p+3))) 212 | 213 | // Must be a 32-bit target processor 214 | #define REGISTER_WIDTH 32 215 | 216 | #endif // __ANIMATEDGIF__ 217 | -------------------------------------------------------------------------------- /ESP32ManyRoundScreenTest.ino: -------------------------------------------------------------------------------- 1 | // Animated GIF with Round Display 2 | // 3 | 4 | 5 | #include 6 | #include 7 | #include "AnimatedGIF.h" 8 | 9 | // Examples images 10 | #include "images/hyperspace.h" 11 | #include "images/nostromo.h" 12 | #include "images/hud_1.h" 13 | #include "images/hud_2.h" 14 | #include "images/hud_5.h" 15 | #include "images/hud_6.h" 16 | #include "images/hud_7.h" 17 | #include "images/darthvader.h" 18 | #include "images/x_wing.h" 19 | #include "images/bb8.h" 20 | 21 | // Adjust this value based on the number of displays 22 | const int NUM_DISPLAYS = 6; 23 | // Add more CS pins if you have more displays, each display must have a dedicated pin 24 | const int CS_PINS[NUM_DISPLAYS] = { 19, 22, 21, 32, 33, 26 }; 25 | int currentScreenIndex = 0; 26 | 27 | AnimatedGIF gif_1; 28 | AnimatedGIF gif_2; 29 | AnimatedGIF gif_3; 30 | AnimatedGIF gif_4; 31 | AnimatedGIF gif_5; 32 | AnimatedGIF gif_6; 33 | 34 | 35 | TFT_eSPI tft = TFT_eSPI(); 36 | 37 | unsigned long lastFrameSpeed = 0; 38 | 39 | void setup() { 40 | Serial.begin(115200); 41 | 42 | tft.init(); 43 | for (int i = 0; i < NUM_DISPLAYS; i++) { 44 | pinMode(CS_PINS[i], OUTPUT); 45 | digitalWrite(CS_PINS[i], LOW); // select the display 46 | tft.fillScreen(TFT_BLACK); 47 | tft.setRotation(2); // Adjust Rotation of your screen (0-3) 48 | digitalWrite(CS_PINS[i], HIGH); // Deselect the display 49 | } 50 | openGif(&gif_1, hyperspace, sizeof(hyperspace)); 51 | openGif(&gif_2, hud_6, sizeof(hud_6)); 52 | openGif(&gif_3, nostromo, sizeof(nostromo)); 53 | openGif(&gif_4, x_wing, sizeof(x_wing)); 54 | openGif(&gif_5, hud_2, sizeof(hud_2)); 55 | openGif(&gif_6, bb8, sizeof(bb8)); 56 | } 57 | void loop() { 58 | playGif(&gif_1, 0); 59 | playGif(&gif_2, 1); 60 | playGif(&gif_3, 2); 61 | playGif(&gif_4, 3); 62 | playGif(&gif_5, 4); 63 | playGif(&gif_6, 5); 64 | } 65 | 66 | void openGif(AnimatedGIF *gif, const uint8_t *gifImage, int gifSize) { 67 | gif->begin(BIG_ENDIAN_PIXELS); 68 | if (!gif->open((uint8_t *)gifImage, gifSize, GIFDraw)) { 69 | Serial.printf("Could not open gif \n"); 70 | } 71 | } 72 | 73 | void playGif(AnimatedGIF *gif, int screenIndex) { 74 | currentScreenIndex = screenIndex; 75 | int res = gif->playFrame(false, NULL); 76 | if (res == 0) { 77 | // If no more frames are available, reset the GIF to the beginning 78 | gif->reset(); 79 | gif->playFrame(false, NULL); 80 | } 81 | if (res == -1) { 82 | Serial.printf("Gif Error = %d on screen %d\n", gif->getLastError(), screenIndex); 83 | } 84 | 85 | 86 | if (screenIndex == 0) { 87 | if (lastFrameSpeed == 0) { 88 | lastFrameSpeed = millis(); 89 | } else { 90 | Serial.printf("Screen 0 FPS=%f\n", 1000.0f / (millis() - lastFrameSpeed)); 91 | lastFrameSpeed = millis(); 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /GIFDraw.ino: -------------------------------------------------------------------------------- 1 | // GIFDraw is called by AnimatedGIF library frame to screen 2 | 3 | #define DISPLAY_WIDTH tft.width() 4 | #define DISPLAY_HEIGHT tft.height() 5 | #define BUFFER_SIZE 256 // Optimum is >= GIF width or integral division of width 6 | 7 | #ifdef USE_DMA 8 | uint16_t usTemp[2][BUFFER_SIZE]; // Global to support DMA use 9 | #else 10 | uint16_t usTemp[1][BUFFER_SIZE]; // Global to support DMA use 11 | #endif 12 | bool dmaBuf = 0; 13 | 14 | // Draw a line of image directly on the LCD 15 | void GIFDraw(GIFDRAW *pDraw) 16 | { 17 | digitalWrite(CS_PINS[currentScreenIndex], LOW); // Select the display 18 | tft.startWrite(); 19 | 20 | uint8_t *s; 21 | uint16_t *d, *usPalette; 22 | int x, y, iWidth, iCount; 23 | 24 | // Displ;ay bounds chech and cropping 25 | iWidth = pDraw->iWidth; 26 | if (iWidth + pDraw->iX > DISPLAY_WIDTH) 27 | iWidth = DISPLAY_WIDTH - pDraw->iX; 28 | usPalette = pDraw->pPalette; 29 | y = pDraw->iY + pDraw->y; // current line 30 | if (y >= DISPLAY_HEIGHT || pDraw->iX >= DISPLAY_WIDTH || iWidth < 1) 31 | return; 32 | 33 | // Old image disposal 34 | s = pDraw->pPixels; 35 | if (pDraw->ucDisposalMethod == 2) // restore to background color 36 | { 37 | for (x = 0; x < iWidth; x++) 38 | { 39 | if (s[x] == pDraw->ucTransparent) 40 | s[x] = pDraw->ucBackground; 41 | } 42 | pDraw->ucHasTransparency = 0; 43 | } 44 | 45 | // Apply the new pixels to the main image 46 | if (pDraw->ucHasTransparency) // if transparency used 47 | { 48 | uint8_t *pEnd, c, ucTransparent = pDraw->ucTransparent; 49 | pEnd = s + iWidth; 50 | x = 0; 51 | iCount = 0; // count non-transparent pixels 52 | while (x < iWidth) 53 | { 54 | c = ucTransparent - 1; 55 | d = &usTemp[0][0]; 56 | while (c != ucTransparent && s < pEnd && iCount < BUFFER_SIZE) 57 | { 58 | c = *s++; 59 | if (c == ucTransparent) // done, stop 60 | { 61 | s--; // back up to treat it like transparent 62 | } 63 | else // opaque 64 | { 65 | *d++ = usPalette[c]; 66 | iCount++; 67 | } 68 | } // while looking for opaque pixels 69 | if (iCount) // any opaque pixels? 70 | { 71 | // DMA would degrtade performance here due to short line segments 72 | tft.setAddrWindow(pDraw->iX + x, y, iCount, 1); 73 | tft.pushPixels(usTemp, iCount); 74 | x += iCount; 75 | iCount = 0; 76 | } 77 | // no, look for a run of transparent pixels 78 | c = ucTransparent; 79 | while (c == ucTransparent && s < pEnd) 80 | { 81 | c = *s++; 82 | if (c == ucTransparent) 83 | x++; 84 | else 85 | s--; 86 | } 87 | } 88 | } 89 | else 90 | { 91 | s = pDraw->pPixels; 92 | 93 | // Unroll the first pass to boost DMA performance 94 | // Translate the 8-bit pixels through the RGB565 palette (already byte reversed) 95 | if (iWidth <= BUFFER_SIZE) 96 | for (iCount = 0; iCount < iWidth; iCount++) 97 | usTemp[dmaBuf][iCount] = usPalette[*s++]; 98 | else 99 | for (iCount = 0; iCount < BUFFER_SIZE; iCount++) 100 | usTemp[dmaBuf][iCount] = usPalette[*s++]; 101 | 102 | #ifdef USE_DMA // 71.6 fps (ST7796 84.5 fps) 103 | tft.dmaWait(); 104 | tft.setAddrWindow(pDraw->iX, y, iWidth, 1); 105 | tft.pushPixelsDMA(&usTemp[dmaBuf][0], iCount); 106 | dmaBuf = !dmaBuf; 107 | #else // 57.0 fps 108 | tft.setAddrWindow(pDraw->iX, y, iWidth, 1); 109 | tft.pushPixels(&usTemp[0][0], iCount); 110 | #endif 111 | 112 | iWidth -= iCount; 113 | // Loop if pixel buffer smaller than width 114 | while (iWidth > 0) 115 | { 116 | // Translate the 8-bit pixels through the RGB565 palette (already byte reversed) 117 | if (iWidth <= BUFFER_SIZE) 118 | for (iCount = 0; iCount < iWidth; iCount++) 119 | usTemp[dmaBuf][iCount] = usPalette[*s++]; 120 | else 121 | for (iCount = 0; iCount < BUFFER_SIZE; iCount++) 122 | usTemp[dmaBuf][iCount] = usPalette[*s++]; 123 | 124 | #ifdef USE_DMA 125 | tft.dmaWait(); 126 | tft.pushPixelsDMA(&usTemp[dmaBuf][0], iCount); 127 | dmaBuf = !dmaBuf; 128 | #else 129 | tft.pushPixels(&usTemp[0][0], iCount); 130 | #endif 131 | iWidth -= iCount; 132 | } 133 | } 134 | tft.endWrite(); 135 | digitalWrite(CS_PINS[currentScreenIndex], HIGH); // Deselect the display 136 | } /* GIFDraw() */ 137 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 thelastoutpostworkshop 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Can the ESP32 handle 6 animated GIFs on 6 different Screens at the same time ? 2 | 3 | 4 | Buy Me A Coffee 5 | 6 | 7 | ### Story 8 | https://youtu.be/0YEKLuVf1E4 9 | 10 | ![Usage](https://github.com/thelastoutpostworkshop/ESP32ManyRoundScreenTest/blob/main/images/Screens-Test.png) -------------------------------------------------------------------------------- /gif.h: -------------------------------------------------------------------------------- 1 | // 2 | // GIF Animator 3 | // written by Larry Bank 4 | // bitbank@pobox.com 5 | // Arduino port started 7/5/2020 6 | // Original GIF code written 20+ years ago :) 7 | // The goal of this code is to decode images up to 480x320 8 | // using no more than 22K of RAM (if sent directly to an LCD display) 9 | // 10 | // Copyright 2020 BitBank Software, Inc. All Rights Reserved. 11 | // Licensed under the Apache License, Version 2.0 (the "License"); 12 | // you may not use this file except in compliance with the License. 13 | // You may obtain a copy of the License at 14 | // http://www.apache.org/licenses/LICENSE-2.0 15 | // Unless required by applicable law or agreed to in writing, software 16 | // distributed under the License is distributed on an "AS IS" BASIS, 17 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | // See the License for the specific language governing permissions and 19 | // limitations under the License. 20 | //=========================================================================== 21 | #include "AnimatedGIF.h" 22 | 23 | #ifdef HAL_ESP32_HAL_H_ 24 | #define memcpy_P memcpy 25 | #endif 26 | 27 | static const unsigned char cGIFBits[9] = {1,4,4,4,8,8,8,8,8}; // convert odd bpp values to ones we can handle 28 | 29 | // forward references 30 | static int GIFInit(GIFIMAGE *pGIF); 31 | static int GIFParseInfo(GIFIMAGE *pPage, int bInfoOnly); 32 | static int GIFGetMoreData(GIFIMAGE *pPage); 33 | static void GIFMakePels(GIFIMAGE *pPage, unsigned int code); 34 | static int DecodeLZW(GIFIMAGE *pImage, int iOptions); 35 | static int32_t readMem(GIFFILE *pFile, uint8_t *pBuf, int32_t iLen); 36 | static int32_t seekMem(GIFFILE *pFile, int32_t iPosition); 37 | int GIF_getInfo(GIFIMAGE *pPage, GIFINFO *pInfo); 38 | #if defined( PICO_BUILD ) || defined( __LINUX__ ) || defined( __MCUXPRESSO ) 39 | static int32_t readFile(GIFFILE *pFile, uint8_t *pBuf, int32_t iLen); 40 | static int32_t seekFile(GIFFILE *pFile, int32_t iPosition); 41 | static void closeFile(void *handle); 42 | 43 | // C API 44 | int GIF_openRAM(GIFIMAGE *pGIF, uint8_t *pData, int iDataSize, GIF_DRAW_CALLBACK *pfnDraw) 45 | { 46 | pGIF->iError = GIF_SUCCESS; 47 | pGIF->pfnRead = readMem; 48 | pGIF->pfnSeek = seekMem; 49 | pGIF->pfnDraw = pfnDraw; 50 | pGIF->pfnOpen = NULL; 51 | pGIF->pfnClose = NULL; 52 | pGIF->GIFFile.iSize = iDataSize; 53 | pGIF->GIFFile.pData = pData; 54 | return GIFInit(pGIF); 55 | } /* GIF_openRAM() */ 56 | 57 | #ifdef __LINUX__ 58 | int GIF_openFile(GIFIMAGE *pGIF, const char *szFilename, GIF_DRAW_CALLBACK *pfnDraw) 59 | { 60 | pGIF->iError = GIF_SUCCESS; 61 | pGIF->pfnRead = readFile; 62 | pGIF->pfnSeek = seekFile; 63 | pGIF->pfnDraw = pfnDraw; 64 | pGIF->pfnOpen = NULL; 65 | pGIF->pfnClose = closeFile; 66 | pGIF->GIFFile.fHandle = fopen(szFilename, "r+b"); 67 | if (pGIF->GIFFile.fHandle == NULL) 68 | return 0; 69 | fseek((FILE *)pGIF->GIFFile.fHandle, 0, SEEK_END); 70 | pGIF->GIFFile.iSize = (int)ftell((FILE *)pGIF->GIFFile.fHandle); 71 | fseek((FILE *)pGIF->GIFFile.fHandle, 0, SEEK_SET); 72 | return GIFInit(pGIF); 73 | } /* GIF_openFile() */ 74 | #endif 75 | 76 | void GIF_close(GIFIMAGE *pGIF) 77 | { 78 | if (pGIF->pfnClose) 79 | (*pGIF->pfnClose)(pGIF->GIFFile.fHandle); 80 | } /* GIF_close() */ 81 | 82 | void GIF_begin(GIFIMAGE *pGIF, unsigned char ucPaletteType) 83 | { 84 | memset(pGIF, 0, sizeof(GIFIMAGE)); 85 | pGIF->ucPaletteType = ucPaletteType; 86 | } /* GIF_begin() */ 87 | 88 | void GIF_reset(GIFIMAGE *pGIF) 89 | { 90 | (*pGIF->pfnSeek)(&pGIF->GIFFile, 0); 91 | } /* GIF_reset() */ 92 | 93 | // 94 | // Return value: 95 | // 1 = good decode, more frames exist 96 | // 0 = good decode, no more frames 97 | // -1 = error 98 | // 99 | int GIF_playFrame(GIFIMAGE *pGIF, int *delayMilliseconds, void *pUser) 100 | { 101 | int rc; 102 | 103 | if (delayMilliseconds) 104 | *delayMilliseconds = 0; // clear any old valid 105 | if (pGIF->GIFFile.iPos >= pGIF->GIFFile.iSize-1) // no more data exists 106 | { 107 | (*pGIF->pfnSeek)(&pGIF->GIFFile, 0); // seek to start 108 | } 109 | if (GIFParseInfo(pGIF, 0)) 110 | { 111 | pGIF->pUser = pUser; 112 | if (pGIF->iError == GIF_EMPTY_FRAME) // don't try to decode it 113 | return 0; 114 | rc = DecodeLZW(pGIF, 0); 115 | if (rc != 0) // problem 116 | return -1; 117 | } 118 | else 119 | { 120 | return -1; // error parsing the frame info, we may be at the end of the file 121 | } 122 | // Return 1 for more frames or 0 if this was the last frame 123 | if (delayMilliseconds) // if not NULL, return the frame delay time 124 | *delayMilliseconds = pGIF->iFrameDelay; 125 | return (pGIF->GIFFile.iPos < pGIF->GIFFile.iSize-1); 126 | } /* GIF_playFrame() */ 127 | 128 | int GIF_getCanvasWidth(GIFIMAGE *pGIF) 129 | { 130 | return pGIF->iCanvasWidth; 131 | } /* GIF_getCanvasWidth() */ 132 | 133 | int GIF_getCanvasHeight(GIFIMAGE *pGIF) 134 | { 135 | return pGIF->iCanvasHeight; 136 | } /* GIF_getCanvasHeight() */ 137 | 138 | int GIF_getComment(GIFIMAGE *pGIF, char *pDest) 139 | { 140 | int32_t iOldPos; 141 | 142 | iOldPos = pGIF->GIFFile.iPos; // keep old position 143 | (*pGIF->pfnSeek)(&pGIF->GIFFile, pGIF->iCommentPos); 144 | (*pGIF->pfnRead)(&pGIF->GIFFile, (uint8_t *)pDest, pGIF->sCommentLen); 145 | (*pGIF->pfnSeek)(&pGIF->GIFFile, iOldPos); 146 | pDest[pGIF->sCommentLen] = 0; // zero terminate the string 147 | return (int)pGIF->sCommentLen; 148 | 149 | } /* GIF_getComment() */ 150 | 151 | int GIF_getLastError(GIFIMAGE *pGIF) 152 | { 153 | return pGIF->iError; 154 | } /* GIF_getLastError() */ 155 | 156 | #endif // !__cplusplus 157 | // 158 | // Helper functions for memory based images 159 | // 160 | static int32_t readMem(GIFFILE *pFile, uint8_t *pBuf, int32_t iLen) 161 | { 162 | int32_t iBytesRead; 163 | 164 | iBytesRead = iLen; 165 | if ((pFile->iSize - pFile->iPos) < iLen) 166 | iBytesRead = pFile->iSize - pFile->iPos; 167 | if (iBytesRead <= 0) 168 | return 0; 169 | memcpy(pBuf, &pFile->pData[pFile->iPos], iBytesRead); 170 | pFile->iPos += iBytesRead; 171 | return iBytesRead; 172 | } /* readMem() */ 173 | 174 | static int32_t readFLASH(GIFFILE *pFile, uint8_t *pBuf, int32_t iLen) 175 | { 176 | int32_t iBytesRead; 177 | 178 | iBytesRead = iLen; 179 | if ((pFile->iSize - pFile->iPos) < iLen) 180 | iBytesRead = pFile->iSize - pFile->iPos; 181 | if (iBytesRead <= 0) 182 | return 0; 183 | memcpy_P(pBuf, &pFile->pData[pFile->iPos], iBytesRead); 184 | pFile->iPos += iBytesRead; 185 | return iBytesRead; 186 | } /* readFLASH() */ 187 | 188 | static int32_t seekMem(GIFFILE *pFile, int32_t iPosition) 189 | { 190 | if (iPosition < 0) iPosition = 0; 191 | else if (iPosition >= pFile->iSize) iPosition = pFile->iSize-1; 192 | pFile->iPos = iPosition; 193 | return iPosition; 194 | } /* seekMem() */ 195 | 196 | #if defined ( __LINUX__ ) || defined( __MCUXPRESSO ) 197 | static void closeFile(void *handle) 198 | { 199 | fclose((FILE *)handle); 200 | } /* closeFile() */ 201 | 202 | static int32_t seekFile(GIFFILE *pFile, int32_t iPosition) 203 | { 204 | if (iPosition < 0) iPosition = 0; 205 | else if (iPosition >= pFile->iSize) iPosition = pFile->iSize-1; 206 | pFile->iPos = iPosition; 207 | fseek((FILE *)pFile->fHandle, iPosition, SEEK_SET); 208 | return iPosition; 209 | } /* seekMem() */ 210 | 211 | static int32_t readFile(GIFFILE *pFile, uint8_t *pBuf, int32_t iLen) 212 | { 213 | int32_t iBytesRead; 214 | 215 | iBytesRead = iLen; 216 | if ((pFile->iSize - pFile->iPos) < iLen) 217 | iBytesRead = pFile->iSize - pFile->iPos; 218 | if (iBytesRead <= 0) 219 | return 0; 220 | iBytesRead = (int)fread(pBuf, 1, iBytesRead, (FILE *)pFile->fHandle); 221 | pFile->iPos += iBytesRead; 222 | return iBytesRead; 223 | } /* readFile() */ 224 | 225 | #endif // __LINUX__ 226 | // 227 | // The following functions are written in plain C and have no 228 | // 3rd party dependencies, not even the C runtime library 229 | // 230 | // 231 | // Initialize a GIF file and callback access from a file on SD or memory 232 | // returns 1 for success, 0 for failure 233 | // Fills in the canvas size of the GIFIMAGE structure 234 | // 235 | static int GIFInit(GIFIMAGE *pGIF) 236 | { 237 | pGIF->GIFFile.iPos = 0; // start at beginning of file 238 | if (!GIFParseInfo(pGIF, 1)) // gather info for the first frame 239 | return 0; // something went wrong; not a GIF file? 240 | (*pGIF->pfnSeek)(&pGIF->GIFFile, 0); // seek back to start of the file 241 | if (pGIF->iCanvasWidth > MAX_WIDTH) { // need to allocate more space 242 | pGIF->iError = GIF_TOO_WIDE; 243 | return 0; 244 | } 245 | return 1; 246 | } /* GIFInit() */ 247 | 248 | // 249 | // Parse the GIF header, gather the size and palette info 250 | // If called with bInfoOnly set to true, it will test for a valid file 251 | // and return the canvas size only 252 | // Returns 1 for success, 0 for failure 253 | // 254 | static int GIFParseInfo(GIFIMAGE *pPage, int bInfoOnly) 255 | { 256 | int i, j, iColorTableBits; 257 | int iBytesRead; 258 | unsigned char c, *p; 259 | int32_t iOffset = 0; 260 | int32_t iStartPos = pPage->GIFFile.iPos; // starting file position 261 | int iReadSize; 262 | 263 | pPage->bUseLocalPalette = 0; // assume no local palette 264 | pPage->bEndOfFrame = 0; // we're just getting started 265 | pPage->iFrameDelay = 0; // may not have a gfx extension block 266 | iReadSize = (bInfoOnly) ? 12 : MAX_CHUNK_SIZE; 267 | // If you try to read past the EOF, the SD lib will return garbage data 268 | if (iStartPos + iReadSize > pPage->GIFFile.iSize) 269 | iReadSize = (pPage->GIFFile.iSize - iStartPos - 1); 270 | p = pPage->ucFileBuf; 271 | iBytesRead = (*pPage->pfnRead)(&pPage->GIFFile, pPage->ucFileBuf, iReadSize); // 255 is plenty for now 272 | 273 | if (iBytesRead != iReadSize) // we're at the end of the file 274 | { 275 | pPage->iError = GIF_EARLY_EOF; 276 | return 0; 277 | } 278 | if (iStartPos == 0) // start of the file 279 | { // canvas size 280 | if (memcmp(p, "GIF89", 5) != 0 && memcmp(p, "GIF87", 5) != 0) // not a GIF file 281 | { 282 | pPage->iError = GIF_BAD_FILE; 283 | return 0; 284 | } 285 | pPage->iCanvasWidth = pPage->iWidth = INTELSHORT(&p[6]); 286 | pPage->iCanvasHeight = pPage->iHeight = INTELSHORT(&p[8]); 287 | pPage->iBpp = ((p[10] & 0x70) >> 4) + 1; 288 | if (bInfoOnly) 289 | return 1; // we've got the info we needed, leave 290 | iColorTableBits = (p[10] & 7) + 1; // Log2(size) of the color table 291 | pPage->ucBackground = p[11]; // background color 292 | pPage->ucGIFBits = 0; 293 | iOffset = 13; 294 | if (p[10] & 0x80) // global color table? 295 | { // by default, convert to byte-reversed RGB565 for immediate use 296 | // Read enough additional data for the color table 297 | iBytesRead += (*pPage->pfnRead)(&pPage->GIFFile, &pPage->ucFileBuf[iBytesRead], 3*(1<ucPaletteType == GIF_PALETTE_RGB565_LE || pPage->ucPaletteType == GIF_PALETTE_RGB565_BE) 299 | { 300 | for (i=0; i<(1<> 3) << 11); // R 304 | usRGB565 |= ((p[iOffset+1] >> 2) << 5); // G 305 | usRGB565 |= (p[iOffset+2] >> 3); // B 306 | if (pPage->ucPaletteType == GIF_PALETTE_RGB565_LE) 307 | pPage->pPalette[i] = usRGB565; 308 | else 309 | pPage->pPalette[i] = __builtin_bswap16(usRGB565); // SPI wants MSB first 310 | iOffset += 3; 311 | } 312 | } 313 | else // just copy it as-is 314 | { 315 | memcpy(pPage->pPalette, &p[iOffset], (1<ucGIFBits = p[iOffset+1]; // packed fields 331 | pPage->iFrameDelay = (INTELSHORT(&p[iOffset+2]))*10; // delay in ms 332 | if (pPage->iFrameDelay <= 1) // 0-1 is going to make it run at 60fps; use 100 (10fps) as a reasonable substitute 333 | pPage->iFrameDelay = 100; 334 | if (pPage->ucGIFBits & 1) // transparent color is used 335 | pPage->ucTransparent = p[iOffset+4]; // transparent color index 336 | iOffset += 6; 337 | } 338 | // else // error 339 | break; 340 | case 0xff: /* App extension */ 341 | c = 1; 342 | while (c) /* Skip all data sub-blocks */ 343 | { 344 | c = p[iOffset++]; /* Block length */ 345 | if ((iBytesRead - iOffset) < (c+32)) // need to read more data first 346 | { 347 | memcpy(pPage->ucFileBuf, &pPage->ucFileBuf[iOffset], (iBytesRead-iOffset)); // move existing data down 348 | iBytesRead -= iOffset; 349 | iStartPos += iOffset; 350 | iOffset = 0; 351 | iBytesRead += (*pPage->pfnRead)(&pPage->GIFFile, &pPage->ucFileBuf[iBytesRead], c+32); 352 | } 353 | if (c == 11) // fixed block length 354 | { // Netscape app block contains the repeat count 355 | if (memcmp(&p[iOffset], "NETSCAPE2.0", 11) == 0) 356 | { 357 | // if (p[iOffset+11] == 3 && p[iOffset+12] == 1) // loop count 358 | // pPage->iRepeatCount = INTELSHORT(&p[iOffset+13]); 359 | } 360 | } 361 | iOffset += (int)c; /* Skip to next sub-block */ 362 | } 363 | break; 364 | case 0x01: /* Text extension */ 365 | c = 1; 366 | j = 0; 367 | while (c) /* Skip all data sub-blocks */ 368 | { 369 | c = p[iOffset++]; /* Block length */ 370 | if (j == 0) // use only first block 371 | { 372 | j = c; 373 | if (j > 127) // max comment length = 127 374 | j = 127; 375 | // memcpy(pPage->szInfo1, &p[iOffset], j); 376 | // pPage->szInfo1[j] = '\0'; 377 | j = 1; 378 | } 379 | iOffset += (int)c; /* Skip this sub-block */ 380 | } 381 | break; 382 | case 0xfe: /* Comment */ 383 | c = 1; 384 | while (c) /* Skip all data sub-blocks */ 385 | { 386 | c = p[iOffset++]; /* Block length */ 387 | if ((iBytesRead - iOffset) < (c+32)) // need to read more data first 388 | { 389 | memcpy(pPage->ucFileBuf, &pPage->ucFileBuf[iOffset], (iBytesRead-iOffset)); // move existing data down 390 | iBytesRead -= iOffset; 391 | iStartPos += iOffset; 392 | iOffset = 0; 393 | iBytesRead += (*pPage->pfnRead)(&pPage->GIFFile, &pPage->ucFileBuf[iBytesRead], c+32); 394 | } 395 | if (pPage->iCommentPos == 0) // Save first block info 396 | { 397 | pPage->iCommentPos = iStartPos + iOffset; 398 | pPage->sCommentLen = c; 399 | } 400 | iOffset += (int)c; /* Skip this sub-block */ 401 | } 402 | break; 403 | default: 404 | /* Bad header info */ 405 | pPage->iError = GIF_DECODE_ERROR; 406 | return 0; 407 | } /* switch */ 408 | } 409 | else // invalid byte, stop decoding 410 | { 411 | if (pPage->GIFFile.iSize - iStartPos < 32) // non-image bytes at end of file? 412 | pPage->iError = GIF_EMPTY_FRAME; 413 | else 414 | /* Bad header info */ 415 | pPage->iError = GIF_DECODE_ERROR; 416 | return 0; 417 | } 418 | } /* while */ 419 | if (p[iOffset] == ';') { // end of file, quit and return a correct error code 420 | pPage->iError = GIF_EMPTY_FRAME; 421 | return 1; 422 | } 423 | 424 | if (p[iOffset] == ',') 425 | iOffset++; 426 | // This particular frame's size and position on the main frame (if animated) 427 | pPage->iX = INTELSHORT(&p[iOffset]); 428 | pPage->iY = INTELSHORT(&p[iOffset+2]); 429 | pPage->iWidth = INTELSHORT(&p[iOffset+4]); 430 | pPage->iHeight = INTELSHORT(&p[iOffset+6]); 431 | iOffset += 8; 432 | 433 | /* Image descriptor 434 | 7 6 5 4 3 2 1 0 M=0 - use global color map, ignore pixel 435 | M I 0 0 0 pixel M=1 - local color map follows, use pixel 436 | I=0 - Image in sequential order 437 | I=1 - Image in interlaced order 438 | pixel+1 = # bits per pixel for this image 439 | */ 440 | pPage->ucMap = p[iOffset++]; 441 | if (pPage->ucMap & 0x80) // local color table? 442 | {// by default, convert to byte-reversed RGB565 for immediate use 443 | j = (1<<((pPage->ucMap & 7)+1)); 444 | // Read enough additional data for the color table 445 | iBytesRead += (*pPage->pfnRead)(&pPage->GIFFile, &pPage->ucFileBuf[iBytesRead], j*3); 446 | if (pPage->ucPaletteType == GIF_PALETTE_RGB565_LE || pPage->ucPaletteType == GIF_PALETTE_RGB565_BE) 447 | { 448 | for (i=0; i> 3) << 11); // R 452 | usRGB565 |= ((p[iOffset+1] >> 2) << 5); // G 453 | usRGB565 |= (p[iOffset+2] >> 3); // B 454 | if (pPage->ucPaletteType == GIF_PALETTE_RGB565_LE) 455 | pPage->pLocalPalette[i] = usRGB565; 456 | else 457 | pPage->pLocalPalette[i] = __builtin_bswap16(usRGB565); // SPI wants MSB first 458 | iOffset += 3; 459 | } 460 | } 461 | else // just copy it as-is 462 | { 463 | memcpy(pPage->pLocalPalette, &p[iOffset], j * 3); 464 | iOffset += j*3; 465 | } 466 | pPage->bUseLocalPalette = 1; 467 | } 468 | pPage->ucCodeStart = p[iOffset++]; /* initial code size */ 469 | /* Since GIF can be 1-8 bpp, we only allow 1,4,8 */ 470 | pPage->iBpp = cGIFBits[pPage->ucCodeStart]; 471 | // we are re-using the same buffer turning GIF file data 472 | // into "pure" LZW 473 | pPage->iLZWSize = 0; // we're starting with no LZW data yet 474 | c = 1; // get chunk length 475 | while (c && iOffset < iBytesRead) 476 | { 477 | // Serial.printf("iOffset=%d, iBytesRead=%d\n", iOffset, iBytesRead); 478 | c = p[iOffset++]; // get chunk length 479 | // Serial.printf("Chunk size = %d\n", c); 480 | if (c <= (iBytesRead - iOffset)) 481 | { 482 | memcpy(&pPage->ucLZW[pPage->iLZWSize], &p[iOffset], c); 483 | pPage->iLZWSize += c; 484 | iOffset += c; 485 | } 486 | else // partial chunk in our buffer 487 | { 488 | int iPartialLen = (iBytesRead - iOffset); 489 | memcpy(&pPage->ucLZW[pPage->iLZWSize], &p[iOffset], iPartialLen); 490 | pPage->iLZWSize += iPartialLen; 491 | iOffset += iPartialLen; 492 | (*pPage->pfnRead)(&pPage->GIFFile, &pPage->ucLZW[pPage->iLZWSize], c - iPartialLen); 493 | pPage->iLZWSize += (c - iPartialLen); 494 | } 495 | if (c == 0) 496 | pPage->bEndOfFrame = 1; // signal not to read beyond the end of the frame 497 | } 498 | // seeking on an SD card is VERY VERY SLOW, so use the data we've already read by de-chunking it 499 | // in this case, there's too much data, so we have to seek backwards a bit 500 | if (iOffset < iBytesRead) 501 | { 502 | // Serial.printf("Need to seek back %d bytes\n", iBytesRead - iOffset); 503 | (*pPage->pfnSeek)(&pPage->GIFFile, iStartPos + iOffset); // position file to new spot 504 | } 505 | return 1; // we are now at the start of the chunk data 506 | } /* GIFParseInfo() */ 507 | // 508 | // Gather info about an animated GIF file 509 | // 510 | int GIF_getInfo(GIFIMAGE *pPage, GIFINFO *pInfo) 511 | { 512 | int iOff, iNumFrames; 513 | int iDelay, iMaxDelay, iMinDelay, iTotalDelay; 514 | int iReadAmount; 515 | int iDataAvailable = 0; 516 | int iDataRemaining = 0; 517 | uint32_t lFileOff = 0; 518 | int bDone = 0; 519 | int bExt; 520 | uint8_t c, *cBuf; 521 | 522 | iMaxDelay = iTotalDelay = 0; 523 | iMinDelay = 10000; 524 | iNumFrames = 1; 525 | iDataRemaining = pPage->GIFFile.iSize; 526 | cBuf = (uint8_t *) pPage->ucFileBuf; 527 | (*pPage->pfnSeek)(&pPage->GIFFile, 0); 528 | iDataAvailable = (*pPage->pfnRead)(&pPage->GIFFile, cBuf, FILE_BUF_SIZE); 529 | iDataRemaining -= iDataAvailable; 530 | lFileOff += iDataAvailable; 531 | iOff = 10; 532 | c = cBuf[iOff]; // get info bits 533 | iOff += 3; /* Skip flags, background color & aspect ratio */ 534 | if (c & 0x80) /* Deal with global color table */ 535 | { 536 | c &= 7; /* Get the number of colors defined */ 537 | iOff += (2<pfnRead)(&pPage->GIFFile, &cBuf[iDataAvailable], FILE_BUF_SIZE-iDataAvailable); 550 | iDataAvailable += iReadAmount; 551 | iDataRemaining -= iReadAmount; 552 | lFileOff += iReadAmount; 553 | } 554 | switch(cBuf[iOff]) 555 | { 556 | case 0x3b: /* End of file */ 557 | /* we were fooled into thinking there were more pages */ 558 | iNumFrames--; 559 | goto gifpagesz; 560 | // F9 = Graphic Control Extension (fixed length of 4 bytes) 561 | // FE = Comment Extension 562 | // FF = Application Extension 563 | // 01 = Plain Text Extension 564 | case 0x21: /* Extension block */ 565 | if (cBuf[iOff+1] == 0xf9 && cBuf[iOff+2] == 4) // Graphic Control Extension 566 | { 567 | //cBuf[iOff+3]; // page disposition flags 568 | iDelay = cBuf[iOff+4]; // delay low byte 569 | iDelay |= (cBuf[iOff+5] << 8); // delay high byte 570 | if (iDelay < 2) // too fast, provide a default 571 | iDelay = 2; 572 | iDelay *= 10; // turn JIFFIES into milliseconds 573 | iTotalDelay += iDelay; 574 | if (iDelay > iMaxDelay) iMaxDelay = iDelay; 575 | else if (iDelay < iMinDelay) iMinDelay = iDelay; 576 | // (cBuf[iOff+6]; // transparent color index 577 | } 578 | iOff += 2; /* skip to length */ 579 | iOff += (int)cBuf[iOff]; /* Skip the data block */ 580 | iOff++; 581 | // block terminator or optional sub blocks 582 | c = cBuf[iOff++]; /* Skip any sub-blocks */ 583 | while (c) 584 | { 585 | iOff += (int)c; 586 | c = cBuf[iOff++]; 587 | if ((iDataAvailable - iOff) < (c+258)) // need to read more data first 588 | { 589 | memcpy(cBuf, &cBuf[iOff], (iDataAvailable-iOff)); // move existing data down 590 | iDataAvailable -= iOff; 591 | iOff = 0; 592 | iReadAmount = (*pPage->pfnRead)(&pPage->GIFFile, &cBuf[iDataAvailable], FILE_BUF_SIZE-iDataAvailable); 593 | iDataAvailable += iReadAmount; 594 | iDataRemaining -= iReadAmount; 595 | lFileOff += iReadAmount; 596 | } 597 | } 598 | if (c != 0) // problem, we went past the end 599 | { 600 | iNumFrames--; // possible corrupt data; stop 601 | goto gifpagesz; 602 | } 603 | break; 604 | case 0x2c: /* Start of image data */ 605 | bExt = 0; /* Stop doing extension blocks */ 606 | break; 607 | default: 608 | /* Corrupt data, stop here */ 609 | iNumFrames--; 610 | goto gifpagesz; 611 | } // switch 612 | } // while 613 | if (iOff >= iDataAvailable) // problem 614 | { 615 | iNumFrames--; // possible corrupt data; stop 616 | goto gifpagesz; 617 | } 618 | /* Start of image data */ 619 | c = cBuf[iOff+9]; /* Get the flags byte */ 620 | iOff += 10; /* Skip image position and size */ 621 | if (c & 0x80) /* Local color table */ 622 | { 623 | c &= 7; 624 | iOff += (2<pfnRead)(&pPage->GIFFile, &cBuf[iDataAvailable], FILE_BUF_SIZE-iDataAvailable); 638 | iDataAvailable += iReadAmount; 639 | iDataRemaining -= iReadAmount; 640 | lFileOff += iReadAmount; 641 | } 642 | c = cBuf[iOff++]; 643 | while (c) /* While there are more data blocks */ 644 | { 645 | if (iOff > (3*FILE_BUF_SIZE/4) && iDataRemaining > 0) /* Near end of buffer, re-align */ 646 | { 647 | memcpy(cBuf, &cBuf[iOff], (iDataAvailable-iOff)); // move existing data down 648 | iDataAvailable -= iOff; 649 | iOff = 0; 650 | iReadAmount = (FILE_BUF_SIZE - iDataAvailable); 651 | if (iReadAmount > iDataRemaining) 652 | iReadAmount = iDataRemaining; 653 | iReadAmount = (*pPage->pfnRead)(&pPage->GIFFile, &cBuf[iDataAvailable], iReadAmount); 654 | iDataAvailable += iReadAmount; 655 | iDataRemaining -= iReadAmount; 656 | lFileOff += iReadAmount; 657 | } 658 | iOff += (int)c; /* Skip this data block */ 659 | // if ((int)lFileOff + iOff > pPage->GIFFile.iSize) // past end of file, stop 660 | // { 661 | // iNumFrames--; // don't count this page 662 | // break; // last page is corrupted, don't use it 663 | // } 664 | c = cBuf[iOff++]; /* Get length of next */ 665 | } 666 | /* End of image data, check for more pages... */ 667 | if (cBuf[iOff] == 0x3b || (iDataRemaining == 0 && (iDataAvailable - iOff) < 32)) 668 | { 669 | bDone = 1; /* End of file has been reached */ 670 | } 671 | else /* More pages to scan */ 672 | { 673 | iNumFrames++; 674 | // read new page data starting at this offset 675 | if (pPage->GIFFile.iSize > FILE_BUF_SIZE && iDataRemaining > 0) // since we didn't read the whole file in one shot 676 | { 677 | memcpy(cBuf, &cBuf[iOff], (iDataAvailable-iOff)); // move existing data down 678 | iDataAvailable -= iOff; 679 | iOff = 0; 680 | iReadAmount = (FILE_BUF_SIZE - iDataAvailable); 681 | if (iReadAmount > iDataRemaining) 682 | iReadAmount = iDataRemaining; 683 | iReadAmount = (*pPage->pfnRead)(&pPage->GIFFile, &cBuf[iDataAvailable], iReadAmount); 684 | iDataAvailable += iReadAmount; 685 | iDataRemaining -= iReadAmount; 686 | lFileOff += iReadAmount; 687 | } 688 | } 689 | } /* while !bDone */ 690 | gifpagesz: 691 | pInfo->iFrameCount = iNumFrames; 692 | pInfo->iMaxDelay = iMaxDelay; 693 | pInfo->iMinDelay = iMinDelay; 694 | pInfo->iDuration = iTotalDelay; 695 | return 1; 696 | } /* GIF_getInfo() */ 697 | 698 | // 699 | // Unpack more chunk data for decoding 700 | // returns 1 to signify more data available for this image 701 | // 0 indicates there is no more data 702 | // 703 | static int GIFGetMoreData(GIFIMAGE *pPage) 704 | { 705 | int iDelta = (pPage->iLZWSize - pPage->iLZWOff); 706 | unsigned char c = 1; 707 | // move any existing data down 708 | if (pPage->bEndOfFrame || iDelta >= (LZW_BUF_SIZE - MAX_CHUNK_SIZE) || iDelta <= 0) 709 | return 1; // frame is finished or buffer is already full; no need to read more data 710 | if (pPage->iLZWOff != 0) 711 | { 712 | // NB: memcpy() fails on some systems because the src and dest ptrs overlap 713 | // so copy the bytes in a simple loop to avoid problems 714 | for (int i=0; iiLZWSize - pPage->iLZWOff; i++) { 715 | pPage->ucLZW[i] = pPage->ucLZW[i + pPage->iLZWOff]; 716 | } 717 | pPage->iLZWSize -= pPage->iLZWOff; 718 | pPage->iLZWOff = 0; 719 | } 720 | while (c && pPage->GIFFile.iPos < pPage->GIFFile.iSize && pPage->iLZWSize < (LZW_BUF_SIZE-MAX_CHUNK_SIZE)) 721 | { 722 | (*pPage->pfnRead)(&pPage->GIFFile, &c, 1); // current length 723 | (*pPage->pfnRead)(&pPage->GIFFile, &pPage->ucLZW[pPage->iLZWSize], c); 724 | pPage->iLZWSize += c; 725 | } 726 | if (c == 0) // end of frame 727 | pPage->bEndOfFrame = 1; 728 | return (c != 0 && pPage->GIFFile.iPos < pPage->GIFFile.iSize); // more data available? 729 | } /* GIFGetMoreData() */ 730 | // 731 | // Handle transparent pixels and disposal method 732 | // Used only when a frame buffer is allocated 733 | // 734 | static void DrawNewPixels(GIFIMAGE *pPage, GIFDRAW *pDraw) 735 | { 736 | uint8_t *d, *s; 737 | int x, iPitch = pPage->iCanvasWidth; 738 | 739 | s = pDraw->pPixels; 740 | d = &pPage->pFrameBuffer[pDraw->iX + (pDraw->y + pDraw->iY) * iPitch]; // dest pointer in our complete canvas buffer 741 | if (pDraw->ucDisposalMethod == 2) // restore to background color 742 | { 743 | memset(d, pDraw->ucBackground, pDraw->iWidth); 744 | } 745 | // Apply the new pixels to the main image 746 | if (pDraw->ucHasTransparency) // if transparency used 747 | { 748 | uint8_t c, ucTransparent = pDraw->ucTransparent; 749 | for (x=0; xiWidth; x++) 750 | { 751 | c = *s++; 752 | if (c != ucTransparent) 753 | *d = c; 754 | d++; 755 | } 756 | } 757 | else 758 | { 759 | memcpy(d, s, pDraw->iWidth); // just overwrite the old pixels 760 | } 761 | } /* DrawNewPixels() */ 762 | // 763 | // Convert current line of pixels through the palette 764 | // to either RGB565 or RGB888 output 765 | // Used only when a frame buffer has been allocated 766 | // 767 | static void ConvertNewPixels(GIFIMAGE *pPage, GIFDRAW *pDraw) 768 | { 769 | uint8_t *d, *s; 770 | int x; 771 | 772 | s = &pPage->pFrameBuffer[(pPage->iCanvasWidth * (pDraw->iY + pDraw->y)) + pDraw->iX]; 773 | d = &pPage->pFrameBuffer[pPage->iCanvasHeight * pPage->iCanvasWidth]; // point past bottom of frame buffer 774 | if (pPage->ucPaletteType == GIF_PALETTE_RGB565_LE || pPage->ucPaletteType == GIF_PALETTE_RGB565_BE) 775 | { 776 | uint16_t *pPal, *pu16; 777 | pPal = (uint16_t *)pDraw->pPalette; 778 | pu16 = (uint16_t *)d; 779 | for (x=0; xiWidth; x++) 780 | { 781 | *pu16++ = pPal[*s++]; // convert to RGB565 pixels 782 | } 783 | } 784 | else 785 | { 786 | uint8_t *pPal; 787 | int pixel; 788 | pPal = (uint8_t *)pDraw->pPalette; 789 | for (x=0; xiWidth; x++) 790 | { 791 | pixel = *s++; 792 | *d++ = pPal[(pixel * 3) + 0]; // convert to RGB888 pixels 793 | *d++ = pPal[(pixel * 3) + 1]; 794 | *d++ = pPal[(pixel * 3) + 2]; 795 | } 796 | } 797 | } /* ConvertNewPixels() */ 798 | 799 | // 800 | // GIFMakePels 801 | // 802 | static void GIFMakePels(GIFIMAGE *pPage, unsigned int code) 803 | { 804 | int iPixCount; 805 | unsigned short *giftabs; 806 | unsigned char *buf, *s, *pEnd, *gifpels; 807 | unsigned char ucNeedMore = 0; 808 | /* Copy this string of sequential pixels to output buffer */ 809 | // iPixCount = 0; 810 | s = pPage->ucFileBuf + FILE_BUF_SIZE; /* Pixels will come out in reversed order */ 811 | buf = pPage->ucLineBuf + (pPage->iWidth - pPage->iXCount); 812 | giftabs = pPage->usGIFTable; 813 | gifpels = &pPage->ucGIFPixels[PIXEL_LAST]; 814 | while (code < LINK_UNUSED) 815 | { 816 | if (s == pPage->ucFileBuf) /* Houston, we have a problem */ 817 | { 818 | return; /* Exit with error */ 819 | } 820 | *(--s) = gifpels[code]; 821 | code = giftabs[code]; 822 | } 823 | iPixCount = (int)(intptr_t)(pPage->ucFileBuf + FILE_BUF_SIZE - s); 824 | 825 | while (iPixCount && pPage->iYCount > 0) 826 | { 827 | if (pPage->iXCount > iPixCount) /* Pixels fit completely on the line */ 828 | { 829 | // memcpy(buf, s, iPixCount); 830 | // buf += iPixCount; 831 | pEnd = buf + iPixCount; 832 | while (buf < pEnd) 833 | { 834 | *buf++ = *s++; 835 | } 836 | pPage->iXCount -= iPixCount; 837 | // iPixCount = 0; 838 | if (ucNeedMore) 839 | GIFGetMoreData(pPage); // check if we need to read more LZW data every 4 lines 840 | return; 841 | } 842 | else /* Pixels cross into next line */ 843 | { 844 | GIFDRAW gd; 845 | pEnd = buf + pPage->iXCount; 846 | while (buf < pEnd) 847 | { 848 | *buf++ = *s++; 849 | } 850 | iPixCount -= pPage->iXCount; 851 | pPage->iXCount = pPage->iWidth; /* Reset pixel count */ 852 | // Prepare GIDRAW structure for callback 853 | gd.iX = pPage->iX; 854 | gd.iY = pPage->iY; 855 | gd.iWidth = pPage->iWidth; 856 | gd.iHeight = pPage->iHeight; 857 | gd.pPixels = pPage->ucLineBuf; 858 | gd.pPalette = (pPage->bUseLocalPalette) ? pPage->pLocalPalette : pPage->pPalette; 859 | gd.pPalette24 = (uint8_t *)gd.pPalette; // just cast the pointer for RGB888 860 | gd.y = pPage->iHeight - pPage->iYCount; 861 | // Ugly logic to handle the interlaced line position, but it 862 | // saves having to have another set of state variables 863 | if (pPage->ucMap & 0x40) { // interlaced? 864 | int height = pPage->iHeight-1; 865 | if (gd.y > height / 2) 866 | gd.y = gd.y * 2 - (height | 1); 867 | else if (gd.y > height / 4) 868 | gd.y = gd.y * 4 - ((height & ~1) | 2); 869 | else if (gd.y > height / 8) 870 | gd.y = gd.y * 8 - ((height & ~3) | 4); 871 | else 872 | gd.y = gd.y * 8; 873 | } 874 | gd.ucDisposalMethod = (pPage->ucGIFBits & 0x1c)>>2; 875 | gd.ucTransparent = pPage->ucTransparent; 876 | gd.ucHasTransparency = pPage->ucGIFBits & 1; 877 | gd.ucBackground = pPage->ucBackground; 878 | gd.pUser = pPage->pUser; 879 | if (pPage->pFrameBuffer) // update the frame buffer 880 | { 881 | DrawNewPixels(pPage, &gd); 882 | if (pPage->ucDrawType == GIF_DRAW_COOKED) 883 | { 884 | ConvertNewPixels(pPage, &gd); // prepare for output 885 | gd.pPixels = &pPage->pFrameBuffer[pPage->iCanvasWidth * pPage->iCanvasHeight]; 886 | } 887 | } 888 | (*pPage->pfnDraw)(&gd); // callback to handle this line 889 | pPage->iYCount--; 890 | buf = pPage->ucLineBuf; 891 | if ((pPage->iYCount & 3) == 0) // since we support only small images... 892 | ucNeedMore = 1; 893 | } 894 | } /* while */ 895 | if (ucNeedMore) 896 | GIFGetMoreData(pPage); // check if we need to read more LZW data every 4 lines 897 | return; 898 | } /* GIFMakePels() */ 899 | // 900 | // Macro to extract a variable length code 901 | // 902 | #define GET_CODE if (bitnum > (REGISTER_WIDTH - codesize)) { pImage->iLZWOff += (bitnum >> 3); \ 903 | bitnum &= 7; ulBits = INTELLONG(&p[pImage->iLZWOff]); } \ 904 | code = (unsigned short) (ulBits >> bitnum); /* Read a 32-bit chunk */ \ 905 | code &= sMask; bitnum += codesize; 906 | 907 | // 908 | // Decode LZW into an image 909 | // 910 | static int DecodeLZW(GIFIMAGE *pImage, int iOptions) 911 | { 912 | int i, bitnum; 913 | unsigned short oldcode, codesize, nextcode, nextlim; 914 | unsigned short *giftabs, cc, eoi; 915 | signed short sMask; 916 | unsigned char *gifpels, *p; 917 | // int iStripSize; 918 | //unsigned char **index; 919 | uint32_t ulBits; 920 | unsigned short code; 921 | (void)iOptions; // not used for now 922 | // if output can be used for string table, do it faster 923 | // if (bGIF && (OutPage->cBitsperpixel == 8 && ((OutPage->iWidth & 3) == 0))) 924 | // return PILFastLZW(InPage, OutPage, bGIF, iOptions); 925 | p = pImage->ucLZW; // un-chunked LZW data 926 | sMask = 0xffff << (pImage->ucCodeStart + 1); 927 | sMask = 0xffff - sMask; 928 | cc = (sMask >> 1) + 1; /* Clear code */ 929 | eoi = cc + 1; 930 | giftabs = pImage->usGIFTable; 931 | gifpels = pImage->ucGIFPixels; 932 | pImage->iYCount = pImage->iHeight; // count down the lines 933 | pImage->iXCount = pImage->iWidth; 934 | bitnum = 0; 935 | pImage->iLZWOff = 0; // Offset into compressed data 936 | GIFGetMoreData(pImage); // Read some data to start 937 | 938 | // Initialize code table 939 | // this part only needs to be initialized once 940 | for (i = 0; i < cc; i++) 941 | { 942 | gifpels[PIXEL_FIRST + i] = gifpels[PIXEL_LAST + i] = (unsigned short) i; 943 | giftabs[i] = LINK_END; 944 | } 945 | init_codetable: 946 | codesize = pImage->ucCodeStart + 1; 947 | sMask = 0xffff << (pImage->ucCodeStart + 1); 948 | sMask = 0xffff - sMask; 949 | nextcode = cc + 2; 950 | nextlim = (unsigned short) ((1 << codesize)); 951 | // This part of the table needs to be reset multiple times 952 | memset(&giftabs[cc], LINK_UNUSED, (4096 - cc)*sizeof(short)); 953 | ulBits = INTELLONG(&p[pImage->iLZWOff]); // start by reading 4 bytes of LZW data 954 | GET_CODE 955 | if (code == cc) // we just reset the dictionary, so get another code 956 | { 957 | GET_CODE 958 | } 959 | oldcode = code; 960 | GIFMakePels(pImage, code); // first code is output as the first pixel 961 | // Main decode loop 962 | while (code != eoi && pImage->iYCount > 0) // && y < pImage->iHeight+1) /* Loop through all lines of the image (or strip) */ 963 | { 964 | GET_CODE 965 | if (code == cc) /* Clear code?, and not first code */ 966 | goto init_codetable; 967 | if (code != eoi) 968 | { 969 | if (nextcode < nextlim) // for deferred cc case, don't let it overwrite the last entry (fff) 970 | { 971 | giftabs[nextcode] = oldcode; 972 | gifpels[PIXEL_FIRST + nextcode] = gifpels[PIXEL_FIRST + oldcode]; 973 | if (giftabs[code] == LINK_UNUSED) /* Old code */ 974 | gifpels[PIXEL_LAST + nextcode] = gifpels[PIXEL_FIRST + oldcode]; 975 | else 976 | gifpels[PIXEL_LAST + nextcode] = gifpels[PIXEL_FIRST + code]; 977 | } 978 | nextcode++; 979 | if (nextcode >= nextlim && codesize < 12) 980 | { 981 | codesize++; 982 | nextlim <<= 1; 983 | sMask = (sMask << 1) | 1; 984 | } 985 | GIFMakePels(pImage, code); 986 | oldcode = code; 987 | } 988 | } /* while not end of LZW code stream */ 989 | return 0; 990 | //gif_forced_error: 991 | // free(pImage->pPixels); 992 | // pImage->pPixels = NULL; 993 | // return -1; 994 | } /* DecodeLZW() */ 995 | 996 | void GIF_setDrawCallback(GIFIMAGE *pGIF, GIF_DRAW_CALLBACK *pfnDraw) 997 | { 998 | pGIF->pfnDraw = pfnDraw; 999 | } /* GIF_setDrawCallback() */ 1000 | // 1001 | // Scale 2 scanlines down by 50% with pixel averaging 1002 | // writes new values over previous line 1003 | // expects RGB565 little endian pixels as input 1004 | // 1005 | void GIF_scaleHalf(uint16_t *pCurrent, uint16_t *pPrev, int iWidth, int bBigEndian) 1006 | { 1007 | int x; 1008 | uint16_t *d = pPrev; 1009 | uint32_t gSum, rbSum, pix0,pix1,pix2,pix3; 1010 | const uint32_t RBMask = 0xf81f, GMask = 0x7e0; 1011 | 1012 | for (x=0; x> 2) & GMask; // for rounding towards 1 1019 | rbSum = (pix0 & RBMask) + (pix1 & RBMask) + (pix2 & RBMask) + (pix3 & RBMask); 1020 | rbSum = ((rbSum + 0x1002) >> 2) & RBMask; 1021 | if (bBigEndian) 1022 | *d++ = __builtin_bswap16((uint16_t)(gSum + rbSum)); 1023 | else 1024 | *d++ = (uint16_t)(gSum + rbSum); // store finished pixel 1025 | } // for x 1026 | } /* GIF_scaleHalf() */ 1027 | 1028 | 1029 | -------------------------------------------------------------------------------- /images/Screens-Test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelastoutpostworkshop/ESP32ManyRoundScreenTest/41a33d4db991c1ca73b3374e62a75b9548a040e3/images/Screens-Test.png --------------------------------------------------------------------------------