How to display Bitmap images from SD on your TFT display using MCUFRIEND_kbv (tutorial)

Had a hard time understanding and using the example sketch that comes with mcufriend, so i made a quick step by step tutorial:

  1. Download imagemagick to convert your jpg into a supported bitmap (Because mcufriend only support uncompressed BMP files, and all
    online converters i tried compress the bmp (idk maybe they dont compress and it didnt work just for me) , we will need to download imagemagick)

  2. Make sure when you install imagemagick you select this checkbox:
    image

  3. open command prompt, the write the following (you should copy your input/output file address then copy and paste it to yourinputfile.jpg/youroutputfile.bmp:

imagemagick convert yourfile.jpg -define bmp:format=bmp4 -compress none youroutputfile.bmp
  1. import your bmp file into the SD card

  2. open Arduino IDE

  3. copy and paste this (not my code, from mcufriend example sketch):

//Demo Function: Display bmp file from TF card
//Arduino IDE: 1.6.5
// Board: Arduino UNO R3, Arduino Mega2560

#include <SPI.h>          // f.k. for Arduino-1.5.2
#include <SD.h>
#include <Adafruit_GFX.h> // Hardware-specific library
#include <MCUFRIEND_kbv.h>

MCUFRIEND_kbv tft;
#define SD_CS 5

File root;
char namebuf[32] = "youroutputfile.bmp";
int pathlen;
uint8_t         spi_save;

void setup()
{
    uint16_t ID;
    Serial.begin(9600);
    Serial.print("Show BMP files on TFT with ID:0x");
    ID = tft.readID();
    Serial.println(ID, HEX);
    if (ID == 0x0D3D3) ID = 0x9481;
    tft.begin(ID);
    tft.fillScreen(0x0000);
  /*  if (tft.height() > tft.width()) tft.setRotation(1);    //LANDSCAPE
    tft.setTextColor(0xFFFF, 0x0000);*/
    bool good = SD.begin(SD_CS);
    if (!good) {
        Serial.print(F("cannot start SD"));
        while (1);
    }
 tft.setRotation(1);

  //call bmpDraw() function:
  bmpDraw("youroutputfile.bmp", 0, 0);
 
 }
 
 void loop()
 {
 }

 // This function opens a Windows Bitmap (BMP) file and
 // displays it at the given coordinates.  It's sped up
 // by reading many pixels worth of data at a time
 // (rather than pixel by pixel).  Increasing the buffer
 // size takes more of the Arduino's precious RAM but
 // makes loading a little faster.	20 pixels seems a
 // good balance.
 
#define BUFFPIXEL 20

 void bmpDraw(char *filename, int x, int y) {
   File 	bmpFile;
   int		bmpWidth, bmpHeight;   // W+H in pixels
   uint8_t	bmpDepth;			   // Bit depth (currently must be 24)
   uint32_t bmpImageoffset; 	   // Start of image data in file
   uint32_t rowSize;			   // Not always = bmpWidth; may have padding
   uint8_t	sdbuffer[3*BUFFPIXEL]; // pixel in buffer (R+G+B per pixel)
   uint16_t lcdbuffer[BUFFPIXEL];  // pixel out buffer (16-bit per pixel)
   uint8_t	buffidx = sizeof(sdbuffer); // Current position in sdbuffer
   boolean	goodBmp = false;	   // Set to true on valid header parse
   boolean	flip	= true; 	   // BMP is stored bottom-to-top
   int		w, h, row, col;
   uint8_t	r, g, b;
   uint32_t pos = 0, startTime = millis();
   uint8_t	lcdidx = 0;
   boolean	first = true;
 
   if((x >= tft.width()) || (y >= tft.height())) return;
 
   Serial.println();
   Serial.print("Loading image '");
   Serial.print(filename);
   Serial.println('\'');
   // Open requested file on SD card
   SPCR = spi_save;
   if ((bmpFile = SD.open(filename)) == NULL) {
	 Serial.print("File not found");
	 return;
   }
 
   // Parse BMP header
   if(read16(bmpFile) == 0x4D42) { // BMP signature
	 Serial.print(F("File size: ")); Serial.println(read32(bmpFile));
	 (void)read32(bmpFile); // Read & ignore creator bytes
	 bmpImageoffset = read32(bmpFile); // Start of image data
	 Serial.print(F("Image Offset: ")); Serial.println(bmpImageoffset, DEC);
	 // Read DIB header
	 Serial.print(F("Header size: ")); Serial.println(read32(bmpFile));
	 bmpWidth  = read32(bmpFile);
	 bmpHeight = read32(bmpFile);
	 if(read16(bmpFile) == 1) { // # planes -- must be '1'
	   bmpDepth = read16(bmpFile); // bits per pixel
	   Serial.print(F("Bit Depth: ")); Serial.println(bmpDepth);
	   if((bmpDepth == 24) && (read32(bmpFile) == 0)) { // 0 = uncompressed
 
		 goodBmp = true; // Supported BMP format -- proceed!
		 Serial.print(F("Image size: "));
		 Serial.print(bmpWidth);
		 Serial.print('x');
		 Serial.println(bmpHeight);
 
		 // BMP rows are padded (if needed) to 4-byte boundary
		 rowSize = (bmpWidth * 3 + 3) & ~3;
 
		 // If bmpHeight is negative, image is in top-down order.
		 // This is not canon but has been observed in the wild.
		 if(bmpHeight < 0) {
		   bmpHeight = -bmpHeight;
		   flip 	 = false;
		 }
 
		 // Crop area to be loaded
		 w = bmpWidth;
		 h = bmpHeight;
		 if((x+w-1) >= tft.width())  w = tft.width()  - x;
		 if((y+h-1) >= tft.height()) h = tft.height() - y;
 
		 // Set TFT address window to clipped image bounds
		 SPCR = 0;
		 tft.setAddrWindow(x, y, x+w-1, y+h-1);
 
		 for (row=0; row<h; row++) { // For each scanline...
		   // Seek to start of scan line.  It might seem labor-
		   // intensive to be doing this on every line, but this
		   // method covers a lot of gritty details like cropping
		   // and scanline padding.  Also, the seek only takes
		   // place if the file position actually needs to change
		   // (avoids a lot of cluster math in SD library).
		   if(flip) // Bitmap is stored bottom-to-top order (normal BMP)
			 pos = bmpImageoffset + (bmpHeight - 1 - row) * rowSize;
		   else 	// Bitmap is stored top-to-bottom
			 pos = bmpImageoffset + row * rowSize;
		   SPCR = spi_save;
		   if(bmpFile.position() != pos) { // Need seek?
			 bmpFile.seek(pos);
			 buffidx = sizeof(sdbuffer); // Force buffer reload
		   }
 
		   for (col=0; col<w; col++) { // For each column...
			 // Time to read more pixel data?
			 if (buffidx >= sizeof(sdbuffer)) { // Indeed
			   // Push LCD buffer to the display first
			   if(lcdidx > 0) {
				 SPCR	= 0;
				 tft.pushColors(lcdbuffer, lcdidx, first);
				 lcdidx = 0;
				 first	= false;
			   }
			   SPCR = spi_save;
			   bmpFile.read(sdbuffer, sizeof(sdbuffer));
			   buffidx = 0; // Set index to beginning
			 }
 
			 // Convert pixel from BMP to TFT format
			 b = sdbuffer[buffidx++];
			 g = sdbuffer[buffidx++];
			 r = sdbuffer[buffidx++];
			 lcdbuffer[lcdidx++] = tft.color565(r,g,b);
		   } // end pixel
		 } // end scanline
		 // Write any remaining data to LCD
		 if(lcdidx > 0) {
		   SPCR = 0;
		   tft.pushColors(lcdbuffer, lcdidx, first);
		 } 
		 Serial.print(F("Loaded in "));
		 Serial.print(millis() - startTime);
		 Serial.println(" ms");
	   } // end goodBmp
	 }
   }
 
   bmpFile.close();
   if(!goodBmp) Serial.println("BMP format not recognized.");
 }
 
 // These read 16- and 32-bit types from the SD card file.
 // BMP data is stored little-endian, Arduino is little-endian too.
 // May need to reverse subscript order if porting elsewhere.
 
 uint16_t read16(File f) {
   uint16_t result;
   ((uint8_t *)&result)[0] = f.read(); // LSB
   ((uint8_t *)&result)[1] = f.read(); // MSB
   return result;
 }
 
 uint32_t read32(File f) {
   uint32_t result;
   ((uint8_t *)&result)[0] = f.read(); // LSB
   ((uint8_t *)&result)[1] = f.read();
   ((uint8_t *)&result)[2] = f.read();
   ((uint8_t *)&result)[3] = f.read(); // MSB
   return result;
 }
  1. thats it! make sure you put the correct file name here - bmpDraw("youroutputfile.bmp", 0, 0);
    you can tweak the image placement by changing the other x,y variables.

The MCUFRIEND_kbv example supports several valid BMP formats. e.g. 256-color Palette, 16-bit colors, ... as well as the industry standard 24-bit colour BMP.
The example sketch can be used with most other TFT libraries. You just use the appropriate "set address window" and "write pixels to window" library methods.

I am not aware of any BMP format that supports pixel compression.
The popularity of 24-bit BMP on Arduino is because it requires few resources and can run on a Uno.

Since you have a Mega2560 there is sufficient SRAM to decode JPG files.
And sufficient Flash to store multiple JPG images as byte arrays in FAR PROGMEM.

The Mega2560 is a bit slow at decoding JPG but it is comparable to displaying BMP from SD card.

David.

1 Like

Oh i didnt knew that! I will edit it out. but for some reason every bmp file I tried that was converted online there was an error "not supported format" or something, so I just assumed its because of commpression. but idk, maybe it's just me.

Which PC Image program did you use?
Or which online converter?
What arguments, flags, operations?

Please attach one of these "not supported format" files.

David.

I used Convert to BMP and JPG to BMP | CloudConvert
these are the top results on google when you search for "jpg to bmp".

heres the bmp that isnt working: test.bmp - Google Drive

I have never seen 32-bit colour BMP. A bit pointless when a TFT has got 16-bit colour at best.

If you add this case 32: block to the example sketch it should render the BMP ok.

                    switch (bmpDepth) {          // Convert pixel from BMP to TFT format
                        case 32:
                            b = sdbuffer[buffidx++];
                            g = sdbuffer[buffidx++];
                            r = sdbuffer[buffidx++];
                            buffidx++;
                            color = tft.color565(r, g, b);
                            break;
                        case 24:

The Convert_to_BMP link produces 32-bit colour BMP e.g. your test.bmp image
But the CloudConvert link produces regular 24_bit colour BMP e.g. as supported by every Arduino BMP program.

David.

Oh okay then. Maybe update your library sketch with that. (You made mcufriend right?)
anyway, Thanks for all of your help!

As far as I know, I am the only person that has published Arduino sketches to display "different format" BMP files.

Most PC Paint/Graphics programs default to 24-bit colour.

Most TFT libraries will provide a BMP display example but only for 24-bit colour.

You get smaller files on SD if you use 16-bit colour. And much smaller if you use a 256-colour palette.
Obviously 16-colour palette and monochrome (2-colour palette) BMP are even smaller.
You reduce the SD read time at the expense of a bit more processing.

David.

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