Eternum.cpp
// This all the necesary libaries
#include <SPI.h>
#include <FS.h>
#include <SD.h>

// It initializes the TFT_eSPI object
#include <TFT_eSPI.h>
TFT_eSPI tft = TFT_eSPI();

// JPEG decoder library
#include <JPEGDecoder.h>

//####################################################################################################
// Setup
//####################################################################################################
void setup() {
  Serial.begin(115200);

  // Set all chip selects high to avoid bus contention during initialisation of each peripheral
  digitalWrite(22, HIGH); // Touch controller chip select (if used)
  digitalWrite(15, HIGH); // TFT screen chip select
  digitalWrite( 5, HIGH); // SD card chips select, must use GPIO 5 (ESP32 SS)

  // Initialize the TFT screen
  tft.begin();

  // Initialize the SD card and check if it was successful
  if (!SD.begin(5, tft.getSPIinstance())) {
    // Print an error message if the SD card mount fails
    Serial.println("Card Mount Failed");
    return;
  }
  uint8_t cardType = SD.cardType();

  if (cardType == CARD_NONE) {
    Serial.println("No SD card attached");
    return;
  }

  // It has adaptations for various versions of SD memory types
  Serial.print("SD Card Type: ");
  if (cardType == CARD_MMC) {
    Serial.println("MMC");
  } else if (cardType == CARD_SD) {
    Serial.println("SDSC");
  } else if (cardType == CARD_SDHC) {
    Serial.println("SDHC");
  } else {
    Serial.println("UNKNOWN");
  }

  uint64_t cardSize = SD.cardSize() / (1024 * 1024);
  Serial.printf("SD Card Size: %lluMB\n", cardSize);

  Serial.println("initialisation done.");
}

// An array for all the items to display the first animation
const char* items[] = {
  "/FRM_100.jpg", "/FRM_101.jpg", "/FRM_102.jpg", "/FRM_103.jpg", "/FRM_104.jpg",
  "/FRM_105.jpg", "/FRM_106.jpg", "/FRM_107.jpg", "/FRM_108.jpg", "/FRM_109.jpg",
  "/FRM_110.jpg", "/FRM_111.jpg", "/FRM_112.jpg", "/FRM_113.jpg", "/FRM_114.jpg",
  "/FRM_115.jpg", "/FRM_116.jpg", "/FRM_117.jpg", "/FRM_118.jpg", "/FRM_119.jpg",
  "/FRM_120.jpg", "/FRM_121.jpg", "/FRM_122.jpg", "/FRM_123.jpg", "/FRM_124.jpg",
  "/FRM_125.jpg", "/FRM_126.jpg", "/FRM_127.jpg", "/FRM_128.jpg", "/FRM_129.jpg",
  "/FRM_130.jpg", "/FRM_131.jpg", "/FRM_132.jpg", "/FRM_133.jpg", "/FRM_134.jpg",
  "/FRM_135.jpg", "/FRM_136.jpg", "/FRM_137.jpg", "/FRM_138.jpg", "/FRM_139.jpg",
  "/FRM_140.jpg", "/FRM_141.jpg", "/FRM_142.jpg", "/FRM_143.jpg", "/FRM_144.jpg",
  "/FRM_145.jpg", "/FRM_146.jpg", "/FRM_147.jpg", "/FRM_148.jpg", "/FRM_149.jpg",
  "/FRM_150.jpg", "/FRM_151.jpg", "/FRM_152.jpg", "/FRM_153.jpg", "/FRM_154.jpg",
  "/FRM_155.jpg", "/FRM_156.jpg", "/FRM_157.jpg", "/FRM_158.jpg", "/FRM_159.jpg",
  "/FRM_160.jpg", "/FRM_161.jpg", "/FRM_162.jpg", "/FRM_163.jpg", "/FRM_164.jpg",
  "/FRM_165.jpg", "/FRM_166.jpg", "/FRM_167.jpg", "/FRM_168.jpg", "/FRM_169.jpg",
  "/FRM_170.jpg", "/FRM_171.jpg", "/FRM_172.jpg", "/FRM_173.jpg", "/FRM_174.jpg",
  "/FRM_175.jpg", "/FRM_176.jpg", "/FRM_177.jpg", "/FRM_178.jpg", "/FRM_179.jpg",
  "/FRM_180.jpg", "/FRM_181.jpg", "/FRM_182.jpg", "/FRM_183.jpg", "/FRM_184.jpg",
  "/FRM_185.jpg", "/FRM_186.jpg", "/FRM_187.jpg", "/FRM_188.jpg", "/FRM_189.jpg",
  "/FRM_190.jpg", "/FRM_191.jpg", "/FRM_192.jpg", "/FRM_193.jpg", "/FRM_194.jpg",
  "/FRM_195.jpg", "/FRM_196.jpg", "/FRM_197.jpg", "/FRM_198.jpg", "/FRM_199.jpg"
};

// An array for all the items to display the second animation
const char* new_I[] = {
  "/Talk_1000.jpg", "/Talk_1001.jpg", "/Talk_1002.jpg", "/Talk_1003.jpg", "/Talk_1004.jpg",
  "/Talk_1005.jpg", "/Talk_1006.jpg", "/Talk_1007.jpg", "/Talk_1008.jpg", "/Talk_1009.jpg",
  "/Talk_1010.jpg", "/Talk_1011.jpg", "/Talk_1012.jpg", "/Talk_1013.jpg", "/Talk_1014.jpg",
  "/Talk_1015.jpg", "/Talk_1016.jpg", "/Talk_1017.jpg", "/Talk_1018.jpg", "/Talk_1019.jpg",
  "/Talk_1020.jpg", "/Talk_1021.jpg", "/Talk_1022.jpg", "/Talk_1023.jpg", "/Talk_1024.jpg",
  "/Talk_1025.jpg", "/Talk_1026.jpg", "/Talk_1027.jpg", "/Talk_1028.jpg", "/Talk_1029.jpg",
  "/Talk_1030.jpg", "/Talk_1031.jpg", "/Talk_1032.jpg", "/Talk_1033.jpg", "/Talk_1034.jpg",
  "/Talk_1035.jpg", "/Talk_1036.jpg", "/Talk_1037.jpg", "/Talk_1038.jpg", "/Talk_1039.jpg",
  "/Talk_1040.jpg", "/Talk_1041.jpg", "/Talk_1042.jpg", "/Talk_1043.jpg", "/Talk_1044.jpg",
  "/Talk_1045.jpg", "/Talk_1046.jpg", "/Talk_1047.jpg", "/Talk_1048.jpg", "/Talk_1049.jpg",
  "/Talk_1050.jpg", "/Talk_1051.jpg", "/Talk_1052.jpg", "/Talk_1053.jpg", "/Talk_1054.jpg",
  "/Talk_1055.jpg", "/Talk_1056.jpg", "/Talk_1057.jpg", "/Talk_1058.jpg", "/Talk_1059.jpg",
  "/Talk_1060.jpg", "/Talk_1061.jpg", "/Talk_1062.jpg", "/Talk_1063.jpg", "/Talk_1064.jpg",
  "/Talk_1065.jpg", "/Talk_1066.jpg", "/Talk_1067.jpg", "/Talk_1068.jpg", "/Talk_1069.jpg",
  "/Talk_1070.jpg", "/Talk_1071.jpg", "/Talk_1072.jpg", "/Talk_1073.jpg", "/Talk_1074.jpg",
  "/Talk_1075.jpg", "/Talk_1076.jpg", "/Talk_1077.jpg", "/Talk_1078.jpg", "/Talk_1079.jpg",
  "/Talk_1080.jpg", "/Talk_1081.jpg", "/Talk_1082.jpg", "/Talk_1083.jpg", "/Talk_1084.jpg",
  "/Talk_1085.jpg", "/Talk_1086.jpg", "/Talk_1087.jpg", "/Talk_1088.jpg", "/Talk_1089.jpg",
  "/Talk_1090.jpg", "/Talk_1091.jpg", "/Talk_1092.jpg", "/Talk_1093.jpg", "/Talk_1094.jpg",
  "/Talk_1095.jpg", "/Talk_1096.jpg", "/Talk_1097.jpg", "/Talk_1098.jpg", "/Talk_1099.jpg",
  "/Talk_1100.jpg", "/Talk_1101.jpg", "/Talk_1102.jpg", "/Talk_1103.jpg", "/Talk_1104.jpg",
  "/Talk_1105.jpg", "/Talk_1106.jpg", "/Talk_1107.jpg", "/Talk_1108.jpg", "/Talk_1109.jpg",
  "/Talk_1110.jpg", "/Talk_1111.jpg", "/Talk_1112.jpg", "/Talk_1113.jpg", "/Talk_1114.jpg",
  "/Talk_1115.jpg", "/Talk_1116.jpg", "/Talk_1117.jpg", "/Talk_1118.jpg", "/Talk_1119.jpg",
  "/Talk_1120.jpg", "/Talk_1121.jpg", "/Talk_1122.jpg", "/Talk_1123.jpg", "/Talk_1124.jpg",
  "/Talk_1125.jpg", "/Talk_1126.jpg", "/Talk_1127.jpg", "/Talk_1128.jpg", "/Talk_1129.jpg",
  "/Talk_1130.jpg", "/Talk_1131.jpg", "/Talk_1132.jpg", "/Talk_1133.jpg", "/Talk_1134.jpg",
  "/Talk_1135.jpg", "/Talk_1136.jpg", "/Talk_1137.jpg", "/Talk_1138.jpg", "/Talk_1139.jpg",
  "/Talk_1140.jpg", "/Talk_1141.jpg", "/Talk_1142.jpg", "/Talk_1143.jpg", "/Talk_1144.jpg",
  "/Talk_1145.jpg", "/Talk_1146.jpg", "/Talk_1147.jpg", "/Talk_1148.jpg", "/Talk_1149.jpg",
  "/Talk_1150.jpg", "/Talk_1151.jpg", "/Talk_1152.jpg", "/Talk_1153.jpg", "/Talk_1154.jpg",
  "/Talk_1155.jpg", "/Talk_1156.jpg", "/Talk_1157.jpg", "/Talk_1158.jpg", "/Talk_1159.jpg",
  "/Talk_1160.jpg", "/Talk_1161.jpg", "/Talk_1162.jpg", "/Talk_1163.jpg", "/Talk_1164.jpg",
  "/Talk_1165.jpg", "/Talk_1166.jpg", "/Talk_1167.jpg"
};


//####################################################################################################
// Main loop
//####################################################################################################
void loop() {

  int numItems = sizeof(items) / sizeof(items[0]);
  int menu_I = sizeof(new_I) / sizeof(new_I[0]);

  // Set the correct position
  tft.setRotation(3);
  tft.fillScreen(0xFFFF);


  while (true) {
    // It clears the screen before any iteration
    tft.fillScreen(0xFFFF);  

    // It calculates the correct position for the image
    int x = (tft.width() - 320) / 2;
    int y = (tft.height() - 240) / 2;

    // It reaches all the items in the array and displays
    for (int i = 0; i < numItems; i++) {
      drawSdJpeg(items[i], x, y);
      delay(100);
    }
  }
}

//####################################################################################################
// Draw a JPEG on the TFT pulled from SD Card
//####################################################################################################
// xpos, ypos is top left corner of plotted image
void drawSdJpeg(const char *filename, int xpos, int ypos) {

  // Open the named file (the Jpeg decoder library will close it)
  File jpegFile = SD.open( filename, FILE_READ);  // or, file handle reference for SD library
 
  if ( !jpegFile ) {
    Serial.print("ERROR: File \""); Serial.print(filename); Serial.println ("\" not found!");
    return;
  }

  Serial.println("===========================");
  Serial.print("Drawing file: "); Serial.println(filename);
  Serial.println("===========================");

  // Use one of the following methods to initialise the decoder:
  bool decoded = JpegDec.decodeSdFile(jpegFile);  // Pass the SD file handle to the decoder,
  //bool decoded = JpegDec.decodeSdFile(filename);  // or pass the filename (String or character array)

  if (decoded) {
    // print information about the image to the serial port
    jpegInfo();
    // render the image onto the screen at given coordinates
    jpegRender(xpos, ypos);
  }
  else {
    Serial.println("Jpeg file format not supported!");
  }
}

//####################################################################################################
// Draw a JPEG on the TFT, images will be cropped on the right/bottom sides if they do not fit
//####################################################################################################
// This function assumes xpos,ypos is a valid screen coordinate. For convenience images that do not
// fit totally on the screen are cropped to the nearest MCU size and may leave right/bottom borders.
void jpegRender(int xpos, int ypos) {

  //jpegInfo(); // Print information from the JPEG file (could comment this line out)

  uint16_t *pImg;
  uint16_t mcu_w = JpegDec.MCUWidth;
  uint16_t mcu_h = JpegDec.MCUHeight;
  uint32_t max_x = JpegDec.width;
  uint32_t max_y = JpegDec.height;

  bool swapBytes = tft.getSwapBytes();
  tft.setSwapBytes(true);
  
  // Jpeg images are draw as a set of image block (tiles) called Minimum Coding Units (MCUs)
  // Typically these MCUs are 16x16 pixel blocks
  // Determine the width and height of the right and bottom edge image blocks
  uint32_t min_w = jpg_min(mcu_w, max_x % mcu_w);
  uint32_t min_h = jpg_min(mcu_h, max_y % mcu_h);

  // save the current image block size
  uint32_t win_w = mcu_w;
  uint32_t win_h = mcu_h;

  // record the current time so we can measure how long it takes to draw an image
  uint32_t drawTime = millis();

  // save the coordinate of the right and bottom edges to assist image cropping
  // to the screen size
  max_x += xpos;
  max_y += ypos;

  // Fetch data from the file, decode and display
  while (JpegDec.read()) {    // While there is more data in the file
    pImg = JpegDec.pImage ;   // Decode a MCU (Minimum Coding Unit, typically a 8x8 or 16x16 pixel block)

    // Calculate coordinates of top left corner of current MCU
    int mcu_x = JpegDec.MCUx * mcu_w + xpos;
    int mcu_y = JpegDec.MCUy * mcu_h + ypos;

    // check if the image block size needs to be changed for the right edge
    if (mcu_x + mcu_w <= max_x) win_w = mcu_w;
    else win_w = min_w;

    // check if the image block size needs to be changed for the bottom edge
    if (mcu_y + mcu_h <= max_y) win_h = mcu_h;
    else win_h = min_h;

    // copy pixels into a contiguous block
    if (win_w != mcu_w)
    {
      uint16_t *cImg;
      int p = 0;
      cImg = pImg + win_w;
      for (int h = 1; h < win_h; h++)
      {
        p += mcu_w;
        for (int w = 0; w < win_w; w++)
        {
          *cImg = *(pImg + w + p);
          cImg++;
        }
      }
    }

    // calculate how many pixels must be drawn
    uint32_t mcu_pixels = win_w * win_h;

    // draw image MCU block only if it will fit on the screen
    if (( mcu_x + win_w ) <= tft.width() && ( mcu_y + win_h ) <= tft.height())
      tft.pushImage(mcu_x, mcu_y, win_w, win_h, pImg);
    else if ( (mcu_y + win_h) >= tft.height())
      JpegDec.abort(); // Image has run off bottom of screen so abort decoding
  }

  tft.setSwapBytes(swapBytes);

  showTime(millis() - drawTime); // These lines are for sketch testing only
}

//####################################################################################################
// Print image information to the serial port (optional)
//####################################################################################################
// JpegDec.decodeFile(...) or JpegDec.decodeArray(...) must be called before this info is available!
void jpegInfo() {

  // Print information extracted from the JPEG file
  Serial.println("JPEG image info");
  Serial.println("===============");
  Serial.print("Width      :");
  Serial.println(JpegDec.width);
  Serial.print("Height     :");
  Serial.println(JpegDec.height);
  Serial.print("Components :");
  Serial.println(JpegDec.comps);
  Serial.print("MCU / row  :");
  Serial.println(JpegDec.MCUSPerRow);
  Serial.print("MCU / col  :");
  Serial.println(JpegDec.MCUSPerCol);
  Serial.print("Scan type  :");
  Serial.println(JpegDec.scanType);
  Serial.print("MCU width  :");
  Serial.println(JpegDec.MCUWidth);
  Serial.print("MCU height :");
  Serial.println(JpegDec.MCUHeight);
  Serial.println("===============");
  Serial.println("");
}

//####################################################################################################
// Show the execution time (optional)
//####################################################################################################
// WARNING: for UNO/AVR legacy reasons printing text to the screen with the Mega might not work for
// sketch sizes greater than ~70KBytes because 16-bit address pointers are used in some libraries.

// The Due will work fine with the HX8357_Due library.

void showTime(uint32_t msTime) {
  //tft.setCursor(0, 0);
  //tft.setTextFont(1);
  //tft.setTextSize(2);
  //tft.setTextColor(TFT_WHITE, TFT_BLACK);
  //tft.print(F(" JPEG drawn in "));
  //tft.print(msTime);
  //tft.println(F(" ms "));
  Serial.print(F(" JPEG drawn in "));
  Serial.print(msTime);
  Serial.println(F(" ms "));
}