Skip to content

Instantly share code, notes, and snippets.

@bitbank2
Created August 24, 2020 22:41
Show Gist options
  • Save bitbank2/39988a1be821126683423c53a64ff340 to your computer and use it in GitHub Desktop.
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
// 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