Show BMP image to 2.8 TFT (240x320) from SD card

i have esp8266 connected with SD CARD and 2.8inch TFT screen (ili9341).

what i want is to show BMP image 1.bmp which is on root of SD card to screen

i tried this code but dont work

#include <TFT_eSPI.h>
#include <SPI.h>
#include <SD.h>

#define TFT_DC 2 //D4
#define TFT_CS 5 //D1

#define SD_CS D8   // Define the SD card CS pin
 
TFT_eSPI tft = TFT_eSPI();  // Create an instance of the TFT_eSPI library

void setup() {
  Serial.begin(9600);
  
  // Initialize TFT display
  tft.begin();
  tft.setRotation(1);  // Adjust the rotation as needed

  // Initialize SD card
  if (!SD.begin(SD_CS)) {
    Serial.println("Unable to initialize SD card.");
    return;
  }

  displayBMP("1.bmp");  // Replace "example.bmp" with the actual filename of your BMP image
}

void displayBMP(const char *filename) {
  File file = SD.open(filename, FILE_READ);

  if (!file) {
    Serial.println("Failed to open file for reading");
    return;
  }

  uint16_t header[27]; // BMP header is 54 bytes, but we just need the first 27 bytes
  file.read((uint8_t*)header, sizeof(header));

  uint16_t width = header[18] + (header[19] << 8);
  uint16_t height = header[22] + (header[23] << 8);

  uint16_t imageDataSize = width * height * 2; // BMP uses 2 bytes per pixel
  uint8_t *imageDataBuffer = (uint8_t *)malloc(imageDataSize);

  if (!imageDataBuffer) {
    Serial.println("Memory allocation failed");
    file.close();
    return;
  }

  file.read(imageDataBuffer, imageDataSize);
  file.close();

  // Display the BMP image
  for (int i = 0; i < imageDataSize; i += 2) {
    uint16_t pixel = (imageDataBuffer[i + 1] << 8) | imageDataBuffer[i];
    tft.pushColor(pixel);
  }

  free(imageDataBuffer);
}

void loop() {
  // Your main code here
}

"Don't work" and 'don't compile" are the different things.
Please show a complete compilation error message

changed code. now it runs but nothing shown on TFT screen (only white screen)

also i tested sd card and it work well coz i can read files there using this code

void SD_listFiles(String dir) {
  Serial.println("Listing files:");
  File root = SD.open(dir);
  while (true) {
    File entry = root.openNextFile();
    if (!entry) {
      break;
    }
    Serial.print("  ");
    Serial.println(entry.name());
  }
  root.close();
}

So what's your question now?

i can see that file "1.bmp" is on sd card but i cant display that on tft screen:

so there is a problem with this function

displayBMP("1.bmp");

im getting

Memory allocation failed

image filesize is 224 kb

Did you try output anything to TFT, rather than BMP image?

yes the default examples of TFT_eSPI and it works well

Please edit this part the code:

adding the lines

 Serial.print("Image size = ");
 Serial.println(imageDataSize );

before file.close() line

and show the output

  if (!imageDataBuffer) {
    Serial.println("Memory allocation failed");
     Serial.print("Image size = ");
     Serial.println(imageDataSize );
    file.close();
    return;
  }

Memory allocation failed
Image size = 0

tried to debug :

void displayBMP(const char *filename) {
  File file = SD.open(filename, FILE_READ);

  if (!file) {
    Serial.println("Failed to open file for reading");
    return;
  }

  uint16_t header[27]; // BMP header is 54 bytes, but we just need the first 27 bytes
  file.read((uint8_t*)header, sizeof(header));

  Serial.println("BMP Header:");
  for (int i = 0; i < sizeof(header) / sizeof(header[0]); ++i) {
    Serial.print("Byte ");
    Serial.print(i);
    Serial.print(": ");
    Serial.println(header[i]);
  }

  uint16_t width = header[18] + (header[19] << 8);
  uint16_t height = header[22] + (header[23] << 8);

  Serial.print("Width: ");
  Serial.println(width);
  Serial.print("Height: ");
  Serial.println(height);

  uint16_t imageDataSize = width * height * 2; // BMP uses 2 bytes per pixel
  Serial.print("Calculated Image Size: ");
  Serial.println(imageDataSize);

}

 

BMP Header:
Byte 0: 19778
Byte 1: 33846
Byte 2: 3
Byte 3: 0
Byte 4: 0
Byte 5: 54
Byte 6: 0
Byte 7: 40
Byte 8: 0
Byte 9: 240
Byte 10: 0
Byte 11: 320
Byte 12: 0
Byte 13: 1
Byte 14: 24
Byte 15: 0
Byte 16: 0
Byte 17: 33792
Byte 18: 3
Byte 19: 0
Byte 20: 0
Byte 21: 0
Byte 22: 0
Byte 23: 0
Byte 24: 0
Byte 25: 0
Byte 26: 0
Width: 3
Height: 0
Calculated Image Size: 0

looks like the BMP file is not compatible with the code


void displayBMP(const char *filename) {
  File file = SD.open(filename, FILE_READ);

  if (!file) {
    Serial.println("Failed to open file for reading");
    return;
  }

  uint8_t header[54]; // BMP header is 54 bytes
  file.read(header, sizeof(header));

  uint32_t width = header[18] + (header[19] << 8) + (header[20] << 16) + (header[21] << 24);
  uint32_t height = header[22] + (header[23] << 8) + (header[24] << 16) + (header[25] << 24);

  Serial.print("Width: ");
  Serial.println(width);
  Serial.print("Height: ");
  Serial.println(height);

  uint32_t imageDataSize = width * height * 3; // BMP uses 3 bytes per pixel for 24-bit format
  Serial.print("Calculated Image Size: ");
  Serial.println(imageDataSize);

  // Rest of the code...
}

now output is

Width: 240

Height: 320

Calculated Image Size: 230400

???? - byte can't contains value greater than 255

Seems to me that you treat the data incorrectly.

Look in your code:

it try to allocate a buffer for entire image at once.
The memory size of 8266 typically about 80Kbytes.
Even without taking into account the memory occupied by other data, there is not enough space for your BMP.
This code will not be able to open this image.

You can try to rewrite the code. so that it downloads and outputs the image in parts, but this will require editing the library

Why not run the example code supplied with the library , it will work , but your image needs the same number of pixels as the display ( not some landscape taken on a 5m pixel camera)
Google for the display will also find you lots of code .

Bitmap needs to be in the root of the SD card .

image is BMP 24 bit 240x320

i tested on esp32 ( i thought its more powerfull)

#include <TFT_eSPI.h>
#include <SPI.h>
#include <SD.h>

#define SD_CS 5   // Define the SD card CS pin

TFT_eSPI tft = TFT_eSPI();  // Create an instance of the TFT_eSPI library

void setup() {
  Serial.begin(9600);
  
  // Initialize TFT display
  tft.begin();
  tft.setRotation(1);  // Adjust the rotation as needed

  // Initialize SD card
  if (!SD.begin(SD_CS)) {
    Serial.println("Unable to initialize SD card.");
    return;
  }

}

void displayBMP(const char *filename) {
  File file = SD.open(filename, FILE_READ);

  if (!file) {
    Serial.println("Failed to open file for reading");
    return;
  }

  uint8_t header[54]; // BMP header is 54 bytes
  file.read(header, sizeof(header));

  uint32_t width = header[18] + (header[19] << 8) + (header[20] << 16) + (header[21] << 24);
  uint32_t height = header[22] + (header[23] << 8) + (header[24] << 16) + (header[25] << 24);

  Serial.print("Width: ");
  Serial.println(width);
  Serial.print("Height: ");
  Serial.println(height);

  uint32_t imageDataSize = width * height * 3; // BMP uses 3 bytes per pixel for 24-bit format
  uint8_t *imageDataBuffer = (uint8_t *)malloc(imageDataSize);

  if (!imageDataBuffer) {
    Serial.println("Memory allocation failed");
    file.close();
    return;
  }

  // Read image data into the buffer
  file.read(imageDataBuffer, imageDataSize);

  // Display the BMP image
  Serial.println("Displaying BMP image...");
  for (int i = 0; i < imageDataSize; i += 3) {
    uint16_t pixel = tft.color565(imageDataBuffer[i], imageDataBuffer[i + 1], imageDataBuffer[i + 2]);
    tft.pushColor(pixel);
  }
  Serial.println("BMP image displayed successfully");

  // Free the allocated memory
  free(imageDataBuffer);

  file.close();
}


 void loop() {
  delay(5000);
  displayBMP("/1.bmp");
    delay(5000);

 }

and i get same:

Width: 240

Height: 320

Memory allocation failed

This code works if u add jpg images on sd card so it can show them on screen:
but i think if i convert to BMP it will be easier for ESP to read and show them without consuming lot of resources

// https://github.com/Bodmer/JPEGDecoder

--------------------------------------------------------

#include <SPI.h>

#include <FS.h>
#include <SD.h>

#include <TFT_eSPI.h>
TFT_eSPI tft = TFT_eSPI();

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

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

  // 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(5, tft.getSPIinstance())) {
    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 "));
}


Again, your code for read BMP header is incorrect, you can see it because you got a weird results like Byte 17: 33792

The lines below leads to overflow in in the third and fourth terms:

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.