0

I'm having problems with reading a bitmap file with the SD library. I wrote a small header that reads uncompressed 24-bit bitmap files. It works fine when I try it on Windows 10 MingW with fread() instead of SD.read(). On my UNO it only reads the correct offset address, the other parts are garbage.

For a pure red 1x1 px bitmap I get the following expected output on Windows with MingW:

Fname: ..\rot.bmp
Offset: 00000036
W * H: 1 * 1
Bit# 24
Cmp: 0
ArraySize: 1
---------------------------
RGB: ff0000 

Arduino outputs:

Fname: rot.bmp
Offset: 00000036
W * H: 0 * 2304
Bit# 58
Cmp: 0
ArraySize: 8
---------------------------
Error while reading BMP file

bmp.hpp

#ifndef BMP_HEADER
#define BMP_HEADER

#if defined(ARDUINO_AVR_UNO) || defined(ARDUINO_AVR_MEGA)
#include <SD.h>
#include<stdint.h>
#define BYTE uint8_t
#define WORD uint16_t
#define DWORD uint32_t
#define LONG int32_t
#else
#include<stdlib.h>
#include<windows.h>
#endif // defined(ARDUINO_AVR_UNO) || defined(ARDUINO_AVR_MEGA)
#pragma pack(2)
typedef struct 
{
  BYTE B;
  BYTE G;
  BYTE R;
} pixel;

typedef struct
{
  WORD bfType;
  DWORD bfSize;
  DWORD bfReserved;
  DWORD bfOffset; //offset to byte array
} bfheader;

typedef struct 
{
  DWORD biSize; //size of this header
  LONG biWidth; //image width
  LONG biHeight;//image height
  WORD biPlanes;
  WORD biBitCount; //color depth in bit per pixel
  DWORD biCompression; //0 for none(RGB mode)
  DWORD biSizeImage; //size of image in byte; may be 0 if biCompression is RGB
  LONG biXPelsPerMeter;
  LONG biYPelsPerMeter;
  DWORD biClrUsed; //palete stuff
  DWORD biClrImportant; //more palete stuff
} biheader;

//returns BGR array of bitmap
//header and biheader are assumed to be allocated upon execution
//returns null if unsuccessful
inline pixel* readBMP(const char* filename, bfheader* header, biheader* infoheader)
{
    #if defined(ARDUINO_AVR_UNO) || defined(ARDUINO_AVR_MEGA)  
    SDLib::File f = SD.open(filename);
    if(!f) return NULL;
    f.read((uint8_t *)header, sizeof(bfheader)); // read the 14-byte file header
    f.read((uint8_t *)infoheader, sizeof(biheader)); //read the 40-byte info header
    #else
    FILE* f = fopen(filename, "rb");
    if(f == NULL) return NULL;
    fread(header, sizeof(bfheader), 1, f);
    fread(infoheader, sizeof(biheader), 1, f);
    #endif //defined(ARDUINO_AVR_UNO) || defined(ARDUINO_AVR_MEGA)
  if(infoheader->biCompression != 0 || infoheader->biBitCount != 24) //if the file is not RGB encoded or if its not 24bit 
  { 
    #if defined(ARDUINO_AVR_UNO) || defined(ARDUINO_AVR_MEGA)   
    f.close();
    #else
    fclose(f);
    #endif // defined(ARDUINO_AVR_UNO) || defined(ARDUINO_AVR_MEGA)
    return NULL;
  }
  int size = (int) infoheader->biSizeImage/sizeof(pixel); //get byte size of pixel array
  pixel* data = (pixel*) malloc(sizeof(pixel)*size); // allocate
  #if defined(ARDUINO_AVR_UNO) || defined(ARDUINO_AVR_MEGA)
    f.seek(header->bfOffset); //set file cursor to starting address of RGB array
    f.read((uint8_t *)data, sizeof(uint8_t)*size); // read the rest of the data at once 
    f.close();
  #else
    fseek(f, header->bfOffset, SEEK_SET);
    fread(data, sizeof(pixel), size, f);
    fclose(f);
  #endif //__arm__
  return data;
} 
#endif //BMP_HEADER

readbmp_test.ino

#include<arduino.h>
#include <SPI.h>
#include "bmp.hpp"

void setup() {
  Serial.begin(9600);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
  }
  Serial.print("Initializing SD card...");

  if (!SD.begin(4)) {
    Serial.println("initialization failed!");
    while (1);
  }
  Serial.println("initialization done.");
}

void loop() 
{
  char debug[160];
  char rgbdata[8];
  const char* const files[] = {"rot.bmp", "gruen.bmp", "blau.bmp"};
  int files_len = 3;
  bfheader header;
  biheader infoheader;  
  pixel* image;
  for(int i=0; i<files_len; i++)
  {
    image = readBMP(files[i], &header, &infoheader);
    Serial.println("----------------------------");
    sprintf(debug,  "Fname: %s\n"
                "Offset: %08lx\n"
                "W * H: %ld * %ld\n"
                "Bit# %u\n"
                "Cmp: %lu\n"
                "ArraySize: %lu\n"
                "---------------------------\n", 
                  files[i], 
                  header.bfOffset, 
                  infoheader.biWidth, infoheader.biHeight, 
                  infoheader.biBitCount, 
                  infoheader.biCompression, 
                  infoheader.biSizeImage/sizeof(pixel));
    Serial.print(debug);
    if(image != NULL)
    {

      for(unsigned j=0; j<infoheader.biSizeImage/sizeof(pixel); j++)
      {
        sprintf(rgbdata, "%02x %02x %02x", image[j].R, image[j].G, image[j].B);
        Serial.println(rgbdata);
      }
    }else{
        sprintf(debug, "Error while reading BMP file\n");
        Serial.print(debug);
    }
    free(image);
  }
}

main.cpp //MingW Windows Test

#if defined(ARDUINO_AVR_UNO) || defined(ARDUINO_AVR_MEGA)  //arduino compiler wants to run this main?
#else
#include<stdio.h>
#include "bmp.hpp"

int main(const int argc, const char** argv)
{
    if(argc < 2) return -1;

    bfheader header;
    biheader infoheader;
    for(int i=1; i<argc; i++)
    {      
        pixel* image = readBMP(argv[i], &header, &infoheader);
        if(image != NULL)
        {
            printf("Fname: %s\n"
                    "Offset: %08x\n"
                    "W * H: %d * %d\n"
                    "Bit# %d\n"
                    "Cmp: %d\n"
                    "ArraySize: %d\n"
                    "---------------------------\n", 
                    argv[i], 
                    header.bfOffset, 
                    infoheader.biWidth, infoheader.biHeight, 
                    infoheader.biBitCount, 
                    infoheader.biCompression, 
                    infoheader.biSizeImage/sizeof(pixel));    
            for(int j=0; j<infoheader.biSizeImage/sizeof(pixel); j++)
            {
                printf("RGB: %02x%02x%02x\n", image[j].R, image[j].G, image[j].B);
            }
        }else{
                printf("Error while reading BMP file\n");
                return 1;
        }
        free(image);
    }
    return 0;
}
#endif

I'm really stuck on this, I actually rewrote it a couple times already. I would really appreciate some help.

9
  • print out the number of bytes read with file.read (return value). try to use readBytes instead of read if the count of read bytes doesn't match the expected size
    – Juraj
    Commented Jan 29, 2019 at 8:08
  • @Juraj I tried with with readBytes and read. Same result: The first read returns the expected 14 bytes. The second only returns 2 instead of the expected 40.
    – MatC
    Commented Jan 29, 2019 at 9:14
  • print the structure as bytes with println(b, HEX), to see the raw bytes to check their position in struct fields
    – Juraj
    Commented Jan 29, 2019 at 10:16
  • How would I do so? Serial.println(*infoheader, HEX); results in 'HardwareSerial::println(biheader&, int)'
    – MatC
    Commented Jan 29, 2019 at 10:25
  • 1
    @Juraj Your tipp with the sprintf was pretty good. I turned on all warnings during compile and saw that the datatypes didn't match the format strings. I fixed that and now my header information is displayed correctly. There is sadly still a problem left as the pure red bitmap displays false pixel values: sprintf(rgbdata, "%02x %02x %02x", image[j].R, image[j].G, image[j].B); Serial.println(rgbdata); displays: c5 00 00 dispite the bitmap not containing C5 at all.
    – MatC
    Commented Jan 29, 2019 at 14:05

1 Answer 1

1

f.read((uint8_t *)infoheader, sizeof(infoheader));

should be

f.read((uint8_t *)infoheader, sizeof(biheader));

the size of the variable (pointer) is only 2. you want to read 40 bytes of biheader struct

and

f.read((uint8_t *)data, sizeof(uint8_t)*size)

should be

f.read((uint8_t *)data, sizeof(pixel)*size)


The next problem is %d and %x in sprintf. %d and %x read two bytes on Arduino, not 4, not 1. Print the values without sprintf as Serial.println(number)

4
  • Oh wow, thanks for pointing that out. It reads the expected amount of bytes. However he output is still wrong. Fname: rot.bmp Offset: 00000036 W * H: 0 * 1 Bit# 0 Cmp: 1 ArraySize: 0
    – MatC
    Commented Jan 29, 2019 at 10:05
  • Hey @juraj I answered on your comment on the main post.
    – MatC
    Commented Jan 29, 2019 at 14:16
  • Hey @Juraj, I modified the format string so the correct datatype is expected, lu for uint32_t etc. For the header that works fine now. Pixeldata is still wrong. I followed your advise and tried displaying the Pixeldata using raw Serial.print. Problem still persists. Fname: rot.bmp Offset: 00000036 W * H: 1 * 1 Bit# 24 Cmp: 0 ArraySize: 1 --------------------------- 58 0 0 is my current output
    – MatC
    Commented Jan 29, 2019 at 14:32
  • 1
    @MatC, did it help?
    – Juraj
    Commented Jan 31, 2019 at 11:47

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.