Created
August 24, 2020 22:41
-
-
Save bitbank2/39988a1be821126683423c53a64ff340 to your computer and use it in GitHub Desktop.
A sketch to demonstrate using my GIF and JPEG libraries together on the Adafruit PyPortal to play either type of file found on the SD Card
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// AnimatedGIF + JPEG SDCard example for Adafruit PyPortal | |
// using SdFat library for card reads and DMA for screen updates | |
#include <AnimatedGIF.h> | |
#include <JPEGDEC.h> | |
#include "SPI.h" | |
#include "Adafruit_GFX.h" | |
#include "Adafruit_ILI9341.h" | |
#include <SdFat.h> // Instead of SD library | |
#define DISPLAY_WIDTH 320 | |
#define DISPLAY_HEIGHT 240 | |
static int x_offset, y_offset; | |
#if defined(ENABLE_EXTENDED_TRANSFER_CLASS) // Do want, much faster | |
SdFatEX filesys; | |
#else | |
SdFat filesys; | |
#endif | |
// PyPortal-specific pins | |
#define SD_CS 32 // SD card select | |
#define TFT_D0 34 // Data bit 0 pin (MUST be on PORT byte boundary) | |
#define TFT_WR 26 // Write-strobe pin (CCL-inverted timer output) | |
#define TFT_DC 10 // Data/command pin | |
#define TFT_CS 11 // Chip-select pin | |
#define TFT_RST 24 // Reset pin | |
#define TFT_RD 9 // Read-strobe pin | |
#define TFT_BACKLIGHT 25 // Backlight enable (active high) | |
Adafruit_ILI9341 tft(tft8bitbus, TFT_D0, TFT_WR, TFT_DC, TFT_CS, TFT_RST, TFT_RD); | |
AnimatedGIF gif; | |
// the GIF class structure is slightly larger, so re-use the memory | |
// as a JPEG structure too | |
JPEGDEC *pJPEG = static_cast<JPEGDEC *>((void *)&gif); | |
File f; | |
void * JPEGOpenFile(const char *fname, int32_t *pSize) | |
{ | |
return GIFOpenFile(fname, pSize); | |
} | |
void JPEGCloseFile(void *pHandle) | |
{ | |
GIFCloseFile(pHandle); | |
} | |
int32_t JPEGReadFile(JPEGFILE *pFile, uint8_t *pBuf, int32_t iLen) | |
{ | |
return GIFReadFile((GIFFILE *)pFile, pBuf, iLen); | |
} | |
int32_t JPEGSeekFile(JPEGFILE *pFile, int32_t iPosition) | |
{ | |
return GIFSeekFile((GIFFILE *)pFile, iPosition); | |
} | |
void * GIFOpenFile(const char *fname, int32_t *pSize) | |
{ | |
f = filesys.open(fname); | |
if (f) | |
{ | |
*pSize = f.size(); | |
return (void *)&f; | |
} | |
return NULL; | |
} /* GIFOpenFile() */ | |
void GIFCloseFile(void *pHandle) | |
{ | |
File *f = static_cast<File *>(pHandle); | |
if (f != NULL) | |
f->close(); | |
} /* GIFCloseFile() */ | |
int32_t GIFReadFile(GIFFILE *pFile, uint8_t *pBuf, int32_t iLen) | |
{ | |
int32_t iBytesRead; | |
iBytesRead = iLen; | |
File *f = static_cast<File *>(pFile->fHandle); | |
// Note: If you read a file all the way to the last byte, seek() stops working | |
if ((pFile->iSize - pFile->iPos) < iLen) | |
iBytesRead = pFile->iSize - pFile->iPos - 1; // <-- ugly work-around | |
if (iBytesRead <= 0) | |
return 0; | |
iBytesRead = (int32_t)f->read(pBuf, iBytesRead); | |
pFile->iPos = f->position(); | |
return iBytesRead; | |
} /* GIFReadFile() */ | |
int32_t GIFSeekFile(GIFFILE *pFile, int32_t iPosition) | |
{ | |
// int i = micros(); | |
File *f = static_cast<File *>(pFile->fHandle); | |
f->seek(iPosition); | |
pFile->iPos = (int32_t)f->position(); | |
// i = micros() - i; | |
// Serial.printf("Seek time = %d us\n", i); | |
return pFile->iPos; | |
} /* GIFSeekFile() */ | |
void JPEGDraw(JPEGDRAW *pDraw) | |
{ | |
// Serial.printf("jpeg draw: x,y=%d,%d, cx,cy = %d,%d\n", pDraw->x, pDraw->y, pDraw->iWidth, pDraw->iHeight); | |
// Serial.printf("Pixel 0 = 0x%04x\n", pDraw->pPixels[0]); | |
tft.dmaWait(); // Wait for prior writePixels() to finish | |
tft.setAddrWindow(pDraw->x, pDraw->y, pDraw->iWidth, pDraw->iHeight); | |
tft.writePixels(pDraw->pPixels, pDraw->iWidth * pDraw->iHeight, true, true); // Use DMA, big-endian | |
} /* JPEGDraw() */ | |
// Draw a line of image directly on the LCD | |
void GIFDraw(GIFDRAW *pDraw) | |
{ | |
uint8_t *s; | |
uint16_t *d, *usPalette, usTemp[320]; | |
int x, y; | |
usPalette = pDraw->pPalette; | |
y = pDraw->iY + pDraw->y; // current line | |
s = pDraw->pPixels; | |
if (pDraw->ucDisposalMethod == 2) // restore to background color | |
{ | |
for (x=0; x<pDraw->iWidth; x++) | |
{ | |
if (s[x] == pDraw->ucTransparent) | |
s[x] = pDraw->ucBackground; | |
} | |
pDraw->ucHasTransparency = 0; | |
} | |
// Apply the new pixels to the main image | |
if (pDraw->ucHasTransparency) // if transparency used | |
{ | |
uint8_t *pEnd, c, ucTransparent = pDraw->ucTransparent; | |
int x, iCount; | |
pEnd = s + pDraw->iWidth; | |
x = 0; | |
iCount = 0; // count non-transparent pixels | |
while(x < pDraw->iWidth) | |
{ | |
c = ucTransparent-1; | |
d = usTemp; | |
while (c != ucTransparent && s < pEnd) | |
{ | |
c = *s++; | |
if (c == ucTransparent) // done, stop | |
{ | |
s--; // back up to treat it like transparent | |
} | |
else // opaque | |
{ | |
*d++ = usPalette[c]; | |
iCount++; | |
} | |
} // while looking for opaque pixels | |
if (iCount) // any opaque pixels? | |
{ | |
tft.dmaWait(); // Wait for prior writePixels() to finish | |
tft.setAddrWindow(pDraw->iX+x, y, iCount, 1); | |
tft.writePixels(usTemp, iCount, true, true); // Use DMA, big-endian | |
x += iCount; | |
iCount = 0; | |
} | |
// no, look for a run of transparent pixels | |
c = ucTransparent; | |
while (c == ucTransparent && s < pEnd) | |
{ | |
c = *s++; | |
if (c == ucTransparent) | |
iCount++; | |
else | |
s--; | |
} | |
if (iCount) | |
{ | |
x += iCount; // skip these | |
iCount = 0; | |
} | |
} | |
} | |
else | |
{ | |
s = pDraw->pPixels; | |
// Translate the 8-bit pixels through the RGB565 palette (already byte reversed) | |
for (x=0; x<pDraw->iWidth; x++) | |
usTemp[x] = usPalette[*s++]; | |
tft.dmaWait(); // Wait for prior writePixels() to finish | |
tft.setAddrWindow(pDraw->iX, y, pDraw->iWidth, 1); | |
tft.writePixels(usTemp, pDraw->iWidth, true, true); // Use DMA, big-endian | |
} | |
// tft.dmaWait(); // Wait for last writePixels() to finish | |
} /* GIFDraw() */ | |
void ShowGIF(char *name) | |
{ | |
tft.fillScreen(ILI9341_BLACK); | |
Serial.printf("Playing %s\n", name); | |
if (gif.open(name, GIFOpenFile, GIFCloseFile, GIFReadFile, GIFSeekFile, GIFDraw)) | |
{ | |
x_offset = (DISPLAY_WIDTH - gif.getCanvasWidth())/2; | |
if (x_offset < 0) x_offset = 0; | |
y_offset = (DISPLAY_HEIGHT - gif.getCanvasHeight())/2; | |
if (y_offset < 0) y_offset = 0; | |
Serial.printf("Successfully opened GIF; Canvas size = %d x %d\n", gif.getCanvasWidth(), gif.getCanvasHeight()); | |
Serial.flush(); | |
while (gif.playFrame(true, NULL)) | |
{ | |
} | |
gif.close(); | |
} | |
} /* ShowGIF() */ | |
// | |
// Return true if a file's leaf name starts with a "." (it's been erased) | |
// | |
int ErasedFile(char *fname) | |
{ | |
int iLen = strlen(fname); | |
int i; | |
if (fname[0] == '/') // fully formed pathname | |
{ | |
for (i=iLen-1; i>0; i--) // find start of leaf name | |
{ | |
if (fname[i] == '/') | |
break; | |
} | |
return (fname[i+1] == '.'); // found a dot? | |
} | |
// must be just a leaf name | |
return (fname[0] == '.'); | |
} | |
void setup() { | |
Serial.begin(115200); | |
//while (!Serial); | |
Serial.println("Starting..."); | |
pinMode(TFT_BACKLIGHT, OUTPUT); | |
digitalWrite(TFT_BACKLIGHT, HIGH); // Backlight on | |
// Note - some systems (ESP32?) require an SPI.begin() before calling SD.begin() | |
// this code was tested on a Teensy 4.1 board | |
if(!filesys.begin(SD_CS)) | |
{ | |
Serial.println("SD Card mount failed!"); | |
return; | |
} | |
else | |
{ | |
Serial.println("SD Card mount succeeded!"); | |
} | |
// put your setup code here, to run once: | |
tft.begin(); | |
tft.setRotation(3); // PyPortal native orientation | |
tft.fillScreen(ILI9341_BLACK); | |
tft.startWrite(); // Not sharing TFT bus on PyPortal, just CS once and leave it | |
gif.begin(BIG_ENDIAN_PIXELS); // TFT is big-endian, faster if no byte swaps | |
} | |
void loop() { | |
long lTime; | |
int iFrames; | |
char *szDir = "/JPEG"; // play all GIFs in this directory on the SD card | |
char fname[256], path[256]; | |
File root, temp; | |
// if (gif.open((char *)"/GIF/futurama.gif", GIFOpenFile, GIFCloseFile, GIFReadFile, GIFSeekFile, GIFDraw)) | |
// { | |
// Serial.printf("Successfully opened GIF; Canvas size = %d x %d\n", gif.getCanvasWidth(), gif.getCanvasHeight()); | |
// while (gif.playFrame(true, NULL)) | |
// { | |
// } | |
// gif.close(); | |
// } | |
// delay(4000); | |
while (1) // run forever | |
{ | |
root = filesys.open(szDir); | |
if (root) | |
{ | |
temp = root.openNextFile(); | |
while (temp) | |
{ | |
if (!temp.isDirectory()) // play it | |
{ | |
temp.getName(fname, sizeof(fname)); | |
if (!ErasedFile(fname)) | |
{ | |
strcpy(path, szDir); | |
strcat(path, "/"); | |
strcat(path, fname); | |
// ShowGIF((char *)path); | |
// tft.fillScreen(ILI9341_BLACK); | |
Serial.printf("Playing %s\n", path); | |
if (gif.open((const char *)path, GIFOpenFile, GIFCloseFile, GIFReadFile, GIFSeekFile, GIFDraw)) | |
{ | |
Serial.printf("Successfully opened GIF; Canvas size = %d x %d\n", gif.getCanvasWidth(), gif.getCanvasHeight()); | |
while (gif.playFrame(true, NULL)) | |
{ | |
} | |
gif.close(); | |
} | |
else // try to open it as a JPEG | |
{ | |
// if (pJPEG->open((uint8_t *)thumb_test, sizeof(thumb_test), JPEGDraw)) | |
if (pJPEG->open((const char *)path, JPEGOpenFile, JPEGCloseFile, JPEGReadFile, JPEGSeekFile, JPEGDraw)) | |
{ | |
pJPEG->setPixelType(RGB565_BIG_ENDIAN); | |
if (pJPEG->decode(0,0,JPEG_EXIF_THUMBNAIL)) | |
{ | |
} | |
pJPEG->close(); | |
// delay(2000); | |
} | |
} | |
} | |
} | |
temp.close(); | |
temp = root.openNextFile(); | |
} | |
root.close(); | |
} // root | |
// delay(4000); // pause before restarting | |
} // while | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment