/ Mini_GIF_Players / Mini_GIF_Players.ino
Mini_GIF_Players.ino
1 // SPDX-FileCopyrightText: 2022 Limor Fried for Adafruit Industries 2 // 3 // SPDX-License-Identifier: MIT 4 5 #include <AnimatedGIF.h> 6 #include <SdFat.h> 7 #include <Adafruit_SPIFlash.h> 8 #include <Adafruit_GFX.h> 9 #include <Adafruit_ST7735.h> // Hardware-specific library for ST7735 10 #include <Adafruit_ST7789.h> // Hardware-specific library for ST7789 11 12 #define TFT_CS 7 13 #define TFT_DC 8 14 #define TFT_RST 9 15 16 #define DISPLAY_WIDTH 320 17 #define DISPLAY_HEIGHT 174 18 19 #define GIFDIRNAME "/" 20 #define NUM_LOOPS 5 21 22 #if defined(ARDUINO_ARCH_ESP32) 23 // ESP32 use same flash device that store code. 24 // Therefore there is no need to specify the SPI and SS 25 Adafruit_FlashTransport_ESP32 flashTransport; 26 27 #elif defined(ARDUINO_ARCH_RP2040) 28 // RP2040 use same flash device that store code for file system. Therefore we 29 // only need to specify start address and size (no need SPI or SS) 30 // By default (start=0, size=0), values that match file system setting in 31 // 'Tools->Flash Size' menu selection will be used. 32 // Adafruit_FlashTransport_RP2040 flashTransport; 33 34 // To be compatible with CircuitPython partition scheme (start_address = 1 MB, 35 // size = total flash - 1 MB) use const value (CPY_START_ADDR, CPY_SIZE) or 36 // subclass Adafruit_FlashTransport_RP2040_CPY. Un-comment either of the 37 // following line: 38 // Adafruit_FlashTransport_RP2040 39 // flashTransport(Adafruit_FlashTransport_RP2040::CPY_START_ADDR, 40 // Adafruit_FlashTransport_RP2040::CPY_SIZE); 41 Adafruit_FlashTransport_RP2040_CPY flashTransport; 42 43 #else 44 // On-board external flash (QSPI or SPI) macros should already 45 // defined in your board variant if supported 46 // - EXTERNAL_FLASH_USE_QSPI 47 // - EXTERNAL_FLASH_USE_CS/EXTERNAL_FLASH_USE_SPI 48 #if defined(EXTERNAL_FLASH_USE_QSPI) 49 Adafruit_FlashTransport_QSPI flashTransport; 50 51 #elif defined(EXTERNAL_FLASH_USE_SPI) 52 Adafruit_FlashTransport_SPI flashTransport(EXTERNAL_FLASH_USE_CS, EXTERNAL_FLASH_USE_SPI); 53 54 #else 55 #error No QSPI/SPI flash are defined on your board variant.h ! 56 #endif 57 #endif 58 59 Adafruit_SPIFlash flash(&flashTransport); 60 61 // file system object from SdFat 62 FatFileSystem fatfs; 63 64 Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_RST); 65 AnimatedGIF gif; 66 File32 f, root; 67 68 void * GIFOpenFile(const char *fname, int32_t *pSize) 69 { 70 f = fatfs.open(fname); 71 if (f) 72 { 73 *pSize = f.size(); 74 return (void *)&f; 75 } 76 return NULL; 77 } /* GIFOpenFile() */ 78 79 void GIFCloseFile(void *pHandle) 80 { 81 File32 *f = static_cast<File32 *>(pHandle); 82 if (f != NULL) 83 f->close(); 84 } /* GIFCloseFile() */ 85 86 int32_t GIFReadFile(GIFFILE *pFile, uint8_t *pBuf, int32_t iLen) 87 { 88 int32_t iBytesRead; 89 iBytesRead = iLen; 90 File32 *f = static_cast<File32 *>(pFile->fHandle); 91 // Note: If you read a file all the way to the last byte, seek() stops working 92 if ((pFile->iSize - pFile->iPos) < iLen) 93 iBytesRead = pFile->iSize - pFile->iPos - 1; // <-- ugly work-around 94 if (iBytesRead <= 0) 95 return 0; 96 iBytesRead = (int32_t)f->read(pBuf, iBytesRead); 97 pFile->iPos = f->position(); 98 return iBytesRead; 99 } /* GIFReadFile() */ 100 101 int32_t GIFSeekFile(GIFFILE *pFile, int32_t iPosition) 102 { 103 int i = micros(); 104 File32 *f = static_cast<File32 *>(pFile->fHandle); 105 f->seek(iPosition); 106 pFile->iPos = (int32_t)f->position(); 107 i = micros() - i; 108 // Serial.printf("Seek time = %d us\n", i); 109 return pFile->iPos; 110 } /* GIFSeekFile() */ 111 112 // Draw a line of image directly on the LCD 113 void GIFDraw(GIFDRAW *pDraw) 114 { 115 uint8_t *s; 116 uint16_t *d, *usPalette, usTemp[320]; 117 int x, y, iWidth; 118 119 iWidth = pDraw->iWidth; 120 // Serial.printf("Drawing %d pixels\n", iWidth); 121 122 if (iWidth + pDraw->iX > DISPLAY_WIDTH) 123 iWidth = DISPLAY_WIDTH - pDraw->iX; 124 usPalette = pDraw->pPalette; 125 y = pDraw->iY + pDraw->y; // current line 126 if (y >= DISPLAY_HEIGHT || pDraw->iX >= DISPLAY_WIDTH || iWidth < 1) 127 return; 128 s = pDraw->pPixels; 129 if (pDraw->ucDisposalMethod == 2) // restore to background color 130 { 131 for (x=0; x<iWidth; x++) 132 { 133 if (s[x] == pDraw->ucTransparent) 134 s[x] = pDraw->ucBackground; 135 } 136 pDraw->ucHasTransparency = 0; 137 } 138 139 // Apply the new pixels to the main image 140 if (pDraw->ucHasTransparency) // if transparency used 141 { 142 uint8_t *pEnd, c, ucTransparent = pDraw->ucTransparent; 143 int x, iCount; 144 pEnd = s + iWidth; 145 x = 0; 146 iCount = 0; // count non-transparent pixels 147 while(x < iWidth) 148 { 149 c = ucTransparent-1; 150 d = usTemp; 151 while (c != ucTransparent && s < pEnd) 152 { 153 c = *s++; 154 if (c == ucTransparent) // done, stop 155 { 156 s--; // back up to treat it like transparent 157 } 158 else // opaque 159 { 160 *d++ = usPalette[c]; 161 iCount++; 162 } 163 } // while looking for opaque pixels 164 if (iCount) // any opaque pixels? 165 { 166 tft.startWrite(); 167 tft.setAddrWindow(pDraw->iX+x, y, iCount, 1); 168 tft.writePixels(usTemp, iCount, false, false); 169 tft.endWrite(); 170 x += iCount; 171 iCount = 0; 172 } 173 // no, look for a run of transparent pixels 174 c = ucTransparent; 175 while (c == ucTransparent && s < pEnd) 176 { 177 c = *s++; 178 if (c == ucTransparent) 179 iCount++; 180 else 181 s--; 182 } 183 if (iCount) 184 { 185 x += iCount; // skip these 186 iCount = 0; 187 } 188 } 189 } 190 else 191 { 192 s = pDraw->pPixels; 193 // Translate the 8-bit pixels through the RGB565 palette (already byte reversed) 194 for (x=0; x<iWidth; x++) 195 usTemp[x] = usPalette[*s++]; 196 tft.startWrite(); 197 tft.setAddrWindow(pDraw->iX, y, iWidth, 1); 198 tft.writePixels(usTemp, iWidth, false, false); 199 tft.endWrite(); 200 } 201 } /* GIFDraw() */ 202 203 204 void setup() { 205 Serial.begin(115200); 206 while (!Serial); 207 208 Serial.println("Adafruit SPIFlash Animated GIF Example"); 209 210 // Initialize flash library and check its chip ID. 211 if (!flash.begin()) { 212 Serial.println("Error, failed to initialize flash chip!"); 213 while(1); 214 } 215 Serial.print("Flash chip JEDEC ID: 0x"); Serial.println(flash.getJEDECID(), HEX); 216 217 // First call begin to mount the filesystem. Check that it returns true 218 // to make sure the filesystem was mounted. 219 if (!fatfs.begin(&flash)) { 220 Serial.println("Failed to mount filesystem!"); 221 Serial.println("Was CircuitPython loaded on the board first to create the filesystem?"); 222 while(1); 223 } 224 Serial.println("Mounted filesystem!"); 225 226 if (!root.open(GIFDIRNAME)) { 227 Serial.println("Open dir failed"); 228 } 229 while (f.openNext(&root, O_RDONLY)) { 230 f.printFileSize(&Serial); 231 Serial.write(' '); 232 f.printModifyDateTime(&Serial); 233 Serial.write(' '); 234 f.printName(&Serial); 235 if (f.isDir()) { 236 // Indicate a directory. 237 Serial.write('/'); 238 } 239 Serial.println(); 240 f.close(); 241 } 242 root.close(); 243 244 tft.init(DISPLAY_HEIGHT, DISPLAY_WIDTH); 245 tft.fillScreen(ST77XX_BLUE); 246 tft.setRotation(1); 247 gif.begin(LITTLE_ENDIAN_PIXELS); 248 } 249 250 void loop() { 251 char thefilename[80]; 252 253 if (!root.open(GIFDIRNAME)) { 254 Serial.println("Open GIF directory failed"); 255 while (1); 256 } 257 while (f.openNext(&root, O_RDONLY)) { 258 f.printFileSize(&Serial); 259 Serial.write(' '); 260 f.printModifyDateTime(&Serial); 261 Serial.write(' '); 262 f.printName(&Serial); 263 if (f.isDir()) { 264 // Indicate a directory. 265 Serial.write('/'); 266 } 267 Serial.println(); 268 f.getName(thefilename, sizeof(thefilename)-1); 269 f.close(); 270 if (strstr(thefilename, ".gif") || strstr(thefilename, ".GIF")) { 271 // found a gif mebe! 272 if (gif.open(thefilename, GIFOpenFile, GIFCloseFile, GIFReadFile, GIFSeekFile, GIFDraw)) { 273 GIFINFO gi; 274 Serial.printf("Successfully opened GIF %s; Canvas size = %d x %d\n", thefilename, gif.getCanvasWidth(), gif.getCanvasHeight()); 275 if (gif.getInfo(&gi)) { 276 Serial.printf("frame count: %d\n", gi.iFrameCount); 277 Serial.printf("duration: %d ms\n", gi.iDuration); 278 Serial.printf("max delay: %d ms\n", gi.iMaxDelay); 279 Serial.printf("min delay: %d ms\n", gi.iMinDelay); 280 } 281 // play thru n times 282 for (int loops=0; loops<NUM_LOOPS; loops++) { 283 while (gif.playFrame(true, NULL)); 284 gif.reset(); 285 } 286 gif.close(); 287 } else { 288 Serial.printf("Error opening file %s = %d\n", thefilename, gif.getLastError()); 289 } 290 } 291 } 292 root.close(); 293 }