/ 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  }