Help if possible with running ShowBmp.ino

Please forgive if I didn't upload code properly. I'm trying to figure out how to add additional bmp's to the sketch below. It is the LAND2.bmp that is the addition. As you can read from the serial monitor "didn't find image". I have attempted various other bmp's and the serial monitor reported:

size 0x4B08A

offset 138

header size 124

bad bmp

At this point not sure what to do!

// IMPORTANT: ELEGOO_TFTLCD LIBRARY MUST BE SPECIFICALLY
// CONFIGURED FOR EITHER THE TFT SHIELD OR THE BREAKOUT BOARD.
// SEE RELEVANT COMMENTS IN Elegoo_TFTLCD.h FOR SETUP.
//Technical support:goodtft@163.com

#include <Elegoo_GFX.h>    // Core graphics library
#include <Elegoo_TFTLCD.h> // Hardware-specific library
#include <SD.h>
#include <SPI.h>


// The control pins for the LCD can be assigned to any digital or
// analog pins...but we'll use the analog pins as this allows us to
// double up the pins with the touch screen (see the TFT paint example).
#define LCD_CS A3 // Chip Select goes to Analog 3
#define LCD_CD A2 // Command/Data goes to Analog 2
#define LCD_WR A1 // LCD Write goes to Analog 1
#define LCD_RD A0 // LCD Read goes to Analog 0
#define PIN_SD_CS 10 // Elegoo SD shields and modules: pin 10

#define LCD_RESET A4 // Can alternately just connect to Arduino's reset pin

// When using the BREAKOUT BOARD only, use these 8 data lines to the LCD:
// For the Arduino Uno, Duemilanove, Diecimila, etc.:
//   D0 connects to digital pin 8  (Notice these are
//   D1 connects to digital pin 9   NOT in order!)
//   D2 connects to digital pin 2
//   D3 connects to digital pin 3
//   D4 connects to digital pin 4
//   D5 connects to digital pin 5
//   D6 connects to digital pin 6
//   D7 connects to digital pin 7
// For the Arduino Mega, use digital pins 22 through 29
// (on the 2-row header at the end of the board).

// Assign human-readable names to some common 16-bit color values:
#define	BLACK   0x0000
#define	BLUE    0x001F
#define	RED     0xF800
#define	GREEN   0x07E0
#define CYAN    0x07FF
#define MAGENTA 0xF81F
#define YELLOW  0xFFE0
#define WHITE   0xFFFF

Elegoo_TFTLCD tft(LCD_CS, LCD_CD, LCD_WR, LCD_RD, LCD_RESET);
// If using the shield, all control and data lines are fixed, and
// a simpler declaration can optionally be used:
// Elegoo_TFTLCD tft;


#define MAX_BMP         10                      // bmp file num
#define FILENAME_LEN    20                      // max file name length

const int __Gnbmp_height = 320;                 // bmp hight
const int __Gnbmp_width  = 240;                 // bmp width

unsigned char __Gnbmp_image_offset  = 0;        // offset

int __Gnfile_num = 6;                           // num of file

char __Gsbmp_files[6][FILENAME_LEN] =           // add file name here
{
"flower.bmp",
"tiger.bmp",
"tree.bmp",
"RedRose.bmp",
"Penguins.bmp",
"LAND2.bmp"      

};
File bmpFile;

/*********************************************/
// This procedure reads a bitmap and draws it to the screen
// its sped up by reading many pixels worth of data at a time
// instead of just one pixel at a time. increading the buffer takes
// more RAM but makes the drawing a little faster. 20 pixels' worth
// is probably a good place

#define BUFFPIXEL       60                      // must be a divisor of 240 
#define BUFFPIXEL_X3    180                     // BUFFPIXELx3

void bmpdraw(File f, int x, int y)
{
    bmpFile.seek(__Gnbmp_image_offset);

    uint32_t time = millis();

    uint8_t sdbuffer[BUFFPIXEL_X3];                 // 3 * pixels to buffer

    for (int i=0; i< __Gnbmp_height; i++) {
        for(int j=0; j<(240/BUFFPIXEL); j++) {
            bmpFile.read(sdbuffer, BUFFPIXEL_X3);
            
            uint8_t buffidx = 0;
            int offset_x = j*BUFFPIXEL;
            unsigned int __color[BUFFPIXEL];
            
            for(int k=0; k<BUFFPIXEL; k++) {
                __color[k] = sdbuffer[buffidx+2]>>3;                        // read
                __color[k] = __color[k]<<6 | (sdbuffer[buffidx+1]>>2);      // green
                __color[k] = __color[k]<<5 | (sdbuffer[buffidx+0]>>3);      // blue
                
                buffidx += 3;
            }

	    for (int m = 0; m < BUFFPIXEL; m ++) {
              tft.drawPixel(m+offset_x, i,__color[m]);
	    }
        }
    }
    
    Serial.print(millis() - time, DEC);
    Serial.println(" ms");
}

boolean bmpReadHeader(File f) 
{
    // read header
    uint32_t tmp;
    uint8_t bmpDepth;
    
    if (read16(f) != 0x4D42) {
        // magic bytes missing
        return false;
    }

    // read file size
    tmp = read32(f);
    Serial.print("size 0x");
    Serial.println(tmp, HEX);

    // read and ignore creator bytes
    read32(f);

    __Gnbmp_image_offset = read32(f);
    Serial.print("offset ");
    Serial.println(__Gnbmp_image_offset, DEC);

    // read DIB header
    tmp = read32(f);
    Serial.print("header size ");
    Serial.println(tmp, DEC);
    
    int bmp_width = read32(f);
    int bmp_height = read32(f);
    
    if(bmp_width != __Gnbmp_width || bmp_height != __Gnbmp_height)  {    // if image is not 320x240, return false
        return false;
    }

    if (read16(f) != 1)
    return false;

    bmpDepth = read16(f);
    Serial.print("bitdepth ");
    Serial.println(bmpDepth, DEC);

    if (read32(f) != 0) {
        // compression not supported!
        return false;
    }

    Serial.print("compression ");
    Serial.println(tmp, DEC);

    return true;
}

/*********************************************/
// These read data from the SD card file and convert them to big endian
// (the data is stored in little endian format!)

// LITTLE ENDIAN!
uint16_t read16(File f)
{
    uint16_t d;
    uint8_t b;
    b = f.read();
    d = f.read();
    d <<= 8;
    d |= b;
    return d;
}

// LITTLE ENDIAN!
uint32_t read32(File f)
{
    uint32_t d;
    uint16_t b;

    b = read16(f);
    d = read16(f);
    d <<= 16;
    d |= b;
    return d;
}

void setup(void) {
  Serial.begin(9600);
  Serial.println(F("TFT LCD test"));

#ifdef USE_Elegoo_SHIELD_PINOUT
  Serial.println(F("Using Elegoo 2.4\" TFT Arduino Shield Pinout"));
#else
  Serial.println(F("Using Elegoo 2.4\" TFT Breakout Board Pinout"));
#endif

  Serial.print("TFT size is "); Serial.print(tft.width()); Serial.print("x"); Serial.println(tft.height());

  tft.reset();

  uint16_t identifier = tft.readID();
   
  if(identifier == 0x9325) {
    Serial.println(F("Found ILI9325 LCD driver"));
  } else if(identifier == 0x9328) {
    Serial.println(F("Found ILI9328 LCD driver"));
  } else if(identifier == 0x4535) {
    Serial.println(F("Found LGDP4535 LCD driver"));
  }else if(identifier == 0x7575) {
    Serial.println(F("Found HX8347G LCD driver"));
  } else if(identifier == 0x9341) {
    Serial.println(F("Found ILI9341 LCD driver"));
  } else if(identifier == 0x8357) {
    Serial.println(F("Found HX8357D LCD driver"));
  } else if(identifier==0x0101)
  {     
      identifier=0x9341;
       Serial.println(F("Found 0x9341 LCD driver"));
  }else {
    Serial.print(F("Unknown LCD driver chip: "));
    Serial.println(identifier, HEX);
    Serial.println(F("If using the Elegoo 2.8\" TFT Arduino shield, the line:"));
    Serial.println(F("  #define USE_Elegoo_SHIELD_PINOUT"));
    Serial.println(F("should appear in the library header (Elegoo_TFT.h)."));
    Serial.println(F("If using the breakout board, it should NOT be #defined!"));
    Serial.println(F("Also if using the breakout, double-check that all wiring"));
    Serial.println(F("matches the tutorial."));
    identifier=0x9341;
   
  }
  
  tft.begin(identifier);
  tft.fillScreen(BLUE);
  
  
  
  //Init SD_Card
  pinMode(10, OUTPUT);
   
  if (!SD.begin(10)) {
    Serial.println("initialization failed!");
    tft.setCursor(0, 0);
    tft.setTextColor(WHITE);    
    tft.setTextSize(1);
    tft.println("SD Card Init fail.");   
  }else
  Serial.println("initialization done."); 
}

void loop(void) {
     for(unsigned char i=0; i<__Gnfile_num; i++) {
        bmpFile = SD.open(__Gsbmp_files[i]);
        if (! bmpFile) {
            Serial.println("didnt find image");
            tft.setTextColor(WHITE);    tft.setTextSize(1);
            tft.println("didnt find BMPimage");
            while (1);
        }
   
        if(! bmpReadHeader(bmpFile)) {
            Serial.println("bad bmp");
            tft.setTextColor(WHITE);    tft.setTextSize(1);
            tft.println("bad bmp");
            return;
        }

        bmpdraw(bmpFile, 0, 0);
        bmpFile.close();
        delay(1000);
        delay(1000);
    }
    
}
Serial Monitor:

TFTFT LCD test

Using Elegoo 2.4" TFT Breakout Board Pinout

TFT size is 240x320

Found 0x9341 LCD driver

initialization done.

size 0x38436

offset 54

header size 40

bitdepth 24

compression 40

11570 ms

size 0x38436

offset 54

header size 40

bitdepth 24

compression 40

11573 ms

size 0x38436

offset 54

header size 40

bitdepth 24

compression 40

11569 ms

size 0x38436

offset 54

header size 40

bitdepth 24

compression 40
11570 ms

size 0x38436

offset 54

header size 40

bitdepth 24

compression 40

11569 ms

didnt find image

It is quite likely that you get the error reports from this function:

boolean bmpReadHeader(File f) 
{
    // read header
    uint32_t tmp;
    uint8_t bmpDepth;
    
    if (read16(f) != 0x4D42) {
        // magic bytes missing
        return false;
    }

    // read file size
    tmp = read32(f);
    Serial.print("size 0x");
    Serial.println(tmp, HEX);

    // read and ignore creator bytes
    read32(f);

    __Gnbmp_image_offset = read32(f);
    Serial.print("offset ");
    Serial.println(__Gnbmp_image_offset, DEC);

    // read DIB header
    tmp = read32(f);
    Serial.print("header size ");
    Serial.println(tmp, DEC);
    
    int bmp_width = read32(f);
    int bmp_height = read32(f);
    
    if(bmp_width != __Gnbmp_width || bmp_height != __Gnbmp_height)  {    // if image is not 320x240, return false
        return false;
    }

    if (read16(f) != 1)
    return false;

    bmpDepth = read16(f);
    Serial.print("bitdepth ");
    Serial.println(bmpDepth, DEC);

    if (read32(f) != 0) {
        // compression not supported!
        return false;
    }

    Serial.print("compression ");
    Serial.println(tmp, DEC);

    return true;
}

As you can see there bmpReadHeader() reads the header data from the file and does the following:

  • Check if the header starts with the "magic number "0x4D42". If not return false
  • Otherwise go on, read and print the file size
  • Read and ignore the next four bytes (creator bytes)
  • Read and print the image offset
  • Read and print the DIB header size
  • Read the image width and height
  • Compare width with 240 and height with 320; if one or both are different, return false
  • Otherwise go on and read Planes; if this value is not equal to 1 return false
  • Otherwise go on, read and print "bitdepth" (Bits per pixel)
  • Read and print "compression", if compression is not equal to zero, return false
  • Otherwise print the "compression" value

There is actually a mistake in the routine as you posted it in the last part (print the compression value):

    if (read32(f) != 0) {
        // compression not supported!
        return false;
    }

    Serial.print("compression ");
    Serial.println(tmp, DEC);

As you see the value of tmp is printed which has still the value from "header size". This part should read:

   tmp = read32(f);
    if (tmp != 0) {
        // compression not supported!
        return false;
    }

    Serial.print("compression ");
    Serial.println(tmp, DEC);

You may check your bmp file with a hex editor on your PC to read the header data. You find an explanation of the bmp header content here:

https://en.wikipedia.org/wiki/BMP_file_format

You may try to add this function to your sketch (instead or before you call readBmpHeader()) and check the data of the different bmps using your Arduino. It just prints the header data without any further

There may be some typos in the code as I could not test it, but feel free to give it a try (be aware that if the magic number is not okay the rest of the header data may also be invalid):

void printBmpHeader(File f)
{
  // read header
  uint32_t tmp;
  uint8_t bmpDepth;

  tmp = read16(f);
  Serial.print("Magic no = (should be 0x4D42)");
  Serial.println(tmp, HEX);

  // read file size
  tmp = read32(f);
  Serial.print("size 0x");
  Serial.println(tmp, HEX);

  // read and ignore creator bytes
  read32(f);

  __Gnbmp_image_offset = read32(f);
  Serial.print("offset ");
  Serial.println(__Gnbmp_image_offset, DEC);

  // read DIB header
  tmp = read32(f);
  Serial.print("header size ");
  Serial.println(tmp, DEC);

  int bmp_width = read32(f);
  int bmp_height = read32(f);

  if (bmp_width != __Gnbmp_width || bmp_height != __Gnbmp_height)  {  
    Serial.println("Wrong image size ");
  }
  Serial.print("Width ");
  Serial.print(bmp_width);
  Serial.print("\Height ");
  Serial.println(bmp_height);

  tmp = read16(f);
  Serial.print("No of planes (should be 1) ");
  Serial.println(tmp);

  bmpDepth = read16(f);
  Serial.print("bitdepth ");
  Serial.println(bmpDepth, DEC);

  tmp = read32(f);
  Serial.print("compression (should be 0)");
  Serial.println(tmp, DEC);
}

Good luck!

Thanks for your support. Pardon for picking your brain, but how come the other 5 bmp's with no problem? I was hoping there was something a long the line of the bmp itself. When I read the output from the serial monitor the pattern of the first 5 were identical, then the one I added to the sketch had entirely different size, offset, header size ,and the message, bad bmp. In one instance it gave the message, didn't find image. Anyway thanks again for your help.

For a first test, copy one of your bmps and see if the newly named copy works properly.

Thanks for the suggestion, but I've already tried several iterations. There is something in the way that the 5 sample .bmps are created. Just don't know how? I will press on to other projects for the time being. Again, many thanks!

If they’re not too secret, post some good ones and the bad one here. Perhaps someone will be able to see the important difference.

Or keep adding serial prints to your code to pinpoint where it dislikes the latest bmp.

Can not upload .bmp in here! Not sure why? The first 5 .bmps are apart of the test sketches from Elegoo 2.8 TFT Touch screen. They were loaded on the cd that came with the product. I'm trying at the moment hexeditor in linux, but having no luck!

Just tried your suggestions: 1st. changing the " compression" statement only - reloaded and no disruption. Still no image. Then, 2nd. I inserted "printBmpHeader" above the readBmpHeader, reloaded and same result as above. 3rd. blocked out the " readBmpHeader" statement and reloaded. This time the image was present, but not viewable. So, I get the impression that the bmp file pics I received with the sketch are some how "tailored" to that sketch, then again, I'm probably wrong. Like to hear your thoughts, if you care. Have a great day.

As @wildbill already posted it is quite difficult to support you without access to the bmp files...

My (quite long post above) includes a link to the wiki page regarding bmp file headers. The small code part I posted is just reading and printing the header data.

The intention is that you might load each of the five bmp that work, print their data and compare those data with the header information of the file that does not load. It is quite likely that there is a difference in the way the bmp files are created; e.g. the bmp reading would already stop if the new file does not have the same format (landscape versus portrait).

You could also use e.g. notepad++ (install the HEX editor in the plugin menu) and load the bmp files. It will show you something like this

With the knowledge from the Wiki page you can identify the different header data and find possible differences. At least you could post the results for the files that work and the one that does not ...

Good luck!

I'm presently loading kolourpaint. I believe it is in fact how the bitmap files are created that makes it possible to view them. I'll let you know of the out come. Sorry for not mentioning it at the beginning of this post, but I'm in Linux Mint 21.1. talk to you soon. Thanks for your help!

Ok. Not for sure how to explain who this came about, but here goes. So, I tried to create a new bitmap from a .jpg in kolourpaint. With that done, I imported the .bmp into ImageMagic to resize, saved it to my SD card, edited the program and reloaded. Got the responce: Didn't find image. So, just for the heck of it I took a .bmp that i got off the internet, resized it in ImageMagic, saved it to my SD card, edited the program and reloaded. And it worked! So, more to ponder about! Many thanks to you for your help. Stay safe!

If you want to investigate further there are hex editors for Linux Mint available like

  • hexedit
  • hexer
  • tweak

etc.

To ponder about or not to ponder about unsolved issues that's the question...

:wink:

1 Like

I will get the editor in the morning. Played enough for today. Talk to you soon.

Hi @rfeist11,

I wrote a small Python script that reads and prints the main header data:

#Python Script
import sys

def bytes2int(b):
    res = 0
    for x in b[::-1]:
        res = (res << 8) + ord(x)
    return res    
    

filename = input("Enter Filename (without extension):")
filename = filename + ".bmp"
print("\n\nFilename:\t\t " + filename)
try:
    f = open(filename, "r")
    print("Magic Number:\t\t",f.read(2))
    filesize = bytes2int(f.read(4))
    print("File Size in Bytes:\t",filesize)
    print("\t  in kBytes:\t",round(filesize/1024))
    f.seek(10);
    print("Offset to Pixel Array:\t",bytes2int(f.read(4)));   
    print("DIB Header Size:\t",bytes2int(f.read(4)));   
    print("Image Width:\t\t",bytes2int(f.read(4)));   
    print("Image Height:\t\t",bytes2int(f.read(4)));
    print("No. of Planes:\t\t",bytes2int(f.read(2)));
    print("Bits per Pixel:\t\t",bytes2int(f.read(2)));
    print("Compression:\t\t",bytes2int(f.read(2)));
    f.close()
    print("\n\nExit with CTRL-C")
    while(1):
       pass 
except FileNotFoundError:
    print("Could not open "+filename)

I tested it with Python 3.8.10 (Win 11) and Python 3.7.3 (Raspberry Pi OS). It should also run using more recent Python versions.

  • Copy the script to the directory where the bmp files are
  • Start the script
  • Input one of the bmp file names (without extension, it will be added by the script).
  • Compare the header data of the different files

Hope it helps ...

As it out I searched out hexedit for Linux. Installed it and opened a bmp file. Immediately realizing I had no idea what I was looking at much less what to do if anything. Then I found this YouTube video: https://www.youtube.com/watch?v=tS50VUrkWfs . The instruction at least help me understand the structure of a .bmp. At this moment I am in the process of watch bits of the video starting with the first 2 bytes: 42 4D=BM and so on. I already opened up 3 .bmp's and made an examination. What I have found with 2of the .bmp's , which came with the test programing. (tree.bmp, flower.bmp) The code is identical up to the point of bytes that determine colors and so forth. But the (LANDS2.bmp) shows a totally different profile.

Here are the 3 code files:

![flower|690x387](upload://tusTYsOsdyoTCvZXEs8xpOwQhMB.png

For now I will continue to watch the YouTube video and list the number of bytes and what they control. You are clearly ahead of me on this subject. I will look at the python programming in the near future. I need to learn this first! Anyway this is where I am at to present. Would still look forward to any comments you might have. Thanks

You are on the right track ...

My Python script does not do anything else than reading the data from the BMP file and print the values.

If you study it you will see that it starts with the first two bytes (ASCII characters BM) and goes on for further relevant data

Using the hex editor you will have to identify whether two or four bytes belong together and what they represent.

You can use a Python IDE like IDLE or Thonny to load and execute the script or open a terminal window and call the script from the command line

Yes. I'm recording those groupings and there individual byte count. A long the way I've been converting the hex call outs to decimal so it makes sense. Such as: F0 00 00 00 = 240 pixel and 40 01 00 00 - 40 01(Little endian)= 320 pixel. It is slowly starting to make sense.
Once I have completed the video I will start an analysis of the various .bmp files that I received with the test, I will also start collecting .bmp's off the internet and looking at them. The question comes to mind, " Do I attempt to edit .bmp's only or .jpg's that have been converted to .bmp's?". For now, it's study time! New discoveries ahead. Thanks again. Your input is very enlightening for me. Have a great day. Talk to you soon about my progress.

Update: I'm still blurred eyed from reading the hex code, but while editing and previewing changes I came up with the idea that in some cases certain .bmp's are manageable where as others are not. And it gave me the idea that in most cases I can open the .bmp in KolourPaint and re-size say from 512 x 512, maintain aspect ratio which then changes to 240 x 240, save, then re-size to 240 x 320 without aspect ratio. Save it to SD and edit sketch to include the new .bmp and it works! It always makes me feel better when I a least think I have some control. I guess it comes from the cnc programming I did for about 20 years. I ran into some of the same issues when loading a raster or .bmp file to make a relief carving. You stretch anyway you wanted, but much like when you go to view them, the image would look distorted. But, I will endeavor to persevere. Have a great evening! I'm done playing for the day. Many Thanks

Great that you found a way to create BMP files that fulfill the technical requirements for your sketch.

If the basic pictures do not provide the required aspect ratio there are only few possibilities to handle it:

  • If the main part can be cut out of the image by a mask with the aspect ratio 3x4 you can copy that part into a new image and change the width accordingly.
  • If that's not possible you might create a picture with e.g. 480x640 or more pixels (but keep it to a multiple of the ratio), fill it with black (or any other appropriate background colour), copy the image of interest into that picture and then convert it to the required width/height. The use of a higher resolution than later needed may help to avoid certain artifacts when the resolution is reduced.

Good luck and have fun!

It's been a couple of interesting days delving into the facets of TFT's. Can't thank you enough for all of your guidance. I'm going to take a look at the python script you offered. Might be dropping a line, but I'll pull my hair a little before then. Be safe.