// This sketch if for an ESP32, it draws Jpeg images pulled from an SD Card // onto the TFT. // As well as the TFT_eSPI library you will need the JPEG Decoder library. // A copy can be downloaded here, it is based on the library by Makoto Kurauchi. // https://github.com/Bodmer/JPEGDecoder // Images on SD Card must be put in the root folder (top level) to be found // Use the SD library examples to verify your SD Card interface works! // The example images used to test this sketch can be found in the library // JPEGDecoder/extras folder //---------------------------------------------------------------------------------------------------- #include #include #include #include TFT_eSPI tft = TFT_eSPI(); // JPEG decoder library #include //#################################################################################################### // 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) tft.begin(); if (!SD.begin()) { Serial.println("Card Mount Failed"); return; } uint8_t cardType = SD.cardType(); if (cardType == CARD_NONE) { Serial.println("No SD card attached"); return; } 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."); } //#################################################################################################### // Main loop //#################################################################################################### void loop() { tft.setRotation(2); // portrait tft.fillScreen(random(0xFFFF)); // The image is 300 x 300 pixels so we do some sums to position image in the middle of the screen! // Doing this by reading the image width and height from the jpeg info is left as an exercise! int x = (tft.width() - 300) / 2 - 1; int y = (tft.height() - 300) / 2 - 1; drawSdJpeg("/EagleEye.jpg", x, y); // This draws a jpeg pulled off the SD Card delay(2000); tft.setRotation(2); // portrait tft.fillScreen(random(0xFFFF)); drawSdJpeg("/Baboon40.jpg", 0, 0); // This draws a jpeg pulled off the SD Card delay(2000); tft.setRotation(2); // portrait tft.fillScreen(random(0xFFFF)); drawSdJpeg("/lena20k.jpg", 0, 0); // This draws a jpeg pulled off the SD Card delay(2000); tft.setRotation(1); // landscape tft.fillScreen(random(0xFFFF)); drawSdJpeg("/Mouse480.jpg", 0, 0); // This draws a jpeg pulled off the SD Card delay(2000); while(1); // Wait here } //#################################################################################################### // 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 ")); }