5

I want to convert a bitmap into binary array. My bitmap image is 272*208 pixel monochromatic 1bpp image. I'm confused when the width I get from my image is 16 instead of 272, the height is corrected. And when I skip the bitmap header to get bitmap infomation, I got string of meaningless number in my text file.

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

File bmpImage;
File textFile;

void setup()
{
Serial.begin(9600);
while (!Serial) {
;
}

Serial.print("Initializing SD card...");
if (!SD.begin(53)) {
  Serial.println("initialization failed!");
  return;
}
Serial.println("initialization done.");
int height = 0;
int width = 0;

// Open
bmpImage = SD.open("Circle.bmp", FILE_READ);
textFile = SD.open("test.txt", FILE_WRITE);


bmpImage.seek(0x12);// width in pixel = 16
width = bmpImage.read();
bmpImage.seek(0x16);// height in pixel = 208
height = bmpImage.read();
Serial.println(width);
Serial.println(height);

int imageSize = height*width;

bmpImage.seek(0x36);//skip bitmap header

for(int i = 0; i < height; i ++) {
  for (int j = 0; j < width; j ++) {
    textFile.write(bmpImage.read());
    textFile.write(" ");
  }
  textFile.write("\n");
}

bmpImage.close();
textFile.close();



Serial.println("done write");
}

void loop()
{
  // nothing happens after setup
}
5

That was a very raw way of trying to read a BMP file. There are a lot of assumptions that might give errors.

  1. The file is a BMP file. The header signature is not checked.
  2. The image offset is assumed to be directly after the file header. It is not read.
  3. The image width is less than 256. Only the lowest byte of the 32-bit image width is read. As 272>256 the low byte is 16.
  4. The image height is less than 256. Only the lowest byte of the 32-bit image height is read.

It is a small bitmap but it will take a very long time to read when reading a single byte at a time. There are methods to improve performance but that is an answer to another question.

A more robust method to read the BMP header and image information is to define them as a struct and read.

struct bmp_file_header_t {
  uint16_t signature;
  uint32_t file_size;
  uint16_t reserved[2];
  uint32_t image_offset;
};

struct bmp_image_header_t {
  uint32_t header_size;
  uint32_t image_width;
  uint32_t image_height;
  uint16_t color_planes;
  uint16_t bits_per_pixel;
  uint32_t compression_method;
  uint32_t image_size;
  uint32_t horizontal_resolution;
  uint32_t vertical_resolution;
  uint32_t colors_in_palette;
  uint32_t important_colors;
};

bmpImage = SD.open("Circle.bmp", FILE_READ);
...
// Read the file header
bmp_file_header_t fileHeader;
bmpImage.read(&fileHeader, sizeof(fileHeader));
...
// Check signature
...
// Read the image header
bmp_image_header_t imageHeader;
bmpImage.read(&imageHeader, sizeof(imageHeader));
...
// Check image size and format
...
// Locate the pixels
bmpImage.seek(fileHeader.image_offset);

The file and image information are stored in little-endian so that works just fine on an AVR.

3

First of all I suggest you to read the full specification of the bmp file header (for instance on wikipedia).

Particularly see that

  1. You can have a color table (palette): consequently the starting address of the bitmap data CAN not be 0x36
  2. The pixels are not stored one per byte, but can be packed (see Pixel storage)
  3. There are different type of headers
  4. Width and Height are 4 byte integers, so your width is fine (272 = 256 + 16)

This said, if your bitmap has a standard BITMAPINFOHEADER header, and if your bitmap has a 24 bit standard coding (16 millions of colors) you can do something like this:

int32_t readNbytesInt(File *p_file, int position, byte nBytes)
{
    if (nBytes > 4)
        return 0;

    p_file->seek(position);

    int32_t weight = 1;
    int32_t result = 0;
    for (; nBytes; nBytes--)
    {
        result += weight * p_file->read();
        weight <<= 8;
    }
    return result;
}

void setup()
{
    Serial.begin(9600);
    while (!Serial);

    Serial.print("Initializing SD card...");
    if (!SD.begin(53)) {
        Serial.println("initialization failed!");
        while (1); // <- this is how you should block execution, not with returns
    }
    Serial.println("initialization done.");


    // Open
    File bmpImage = SD.open("Circle.bmp", FILE_READ);
    File textFile = SD.open("test.txt", FILE_WRITE);

    int32_t dataStartingOffset = readNbytesInt(&bmpImage, 0x0A, 4);

    // Change their types to int32_t (4byte)
    int32_t width = readNbytesInt(&bmpImage, 0x12, 4);
    int32_t height = readNbytesInt(&bmpImage, 0x16, 4);
    Serial.println(width);
    Serial.println(height);

    int16_t pixelsize = readNbytesInt(&bmpImage, 0x1C, 2);

    if (pixelsize != 24)
    {
        Serial.println("Image is not 24 bpp");
        while (1);
    }

    bmpImage.seek(dataStartingOffset);//skip bitmap header

    // 24bpp means you have three bytes per pixel, usually B G R

    byte R, G, B;

    for(int32_t i = 0; i < height; i ++) {
        for (int32_t j = 0; j < width; j ++) {
            B = bmpImage.read();
            G = bmpImage.read();
            R = bmpImage.read();
            textFile.print("R");
            textFile.print(R);
            textFile.print("G");
            textFile.print(G);
            textFile.print("B");
            textFile.print(B);
            textFile.print(" ");
        }
        textFile.print("\n");
    }

    bmpImage.close();
    textFile.close();

    Serial.println("done write");
}

The text output of this should be a file with an array of strings like R10G100B25, where R is the red component of the pixel, G is the green component and B the blue one.

Now, this is only compatible with the 24bpp format. If you want to support more formats you will need to read the specs and code them.

Let me know if this works for you

2

Or you could keep your old code with some adjustments, if you just want to read your image without bothering with too many checks.

Change the type of imageSize to unsigned long.

The height and width are 4-byte fields, so a single read() wont cut it. You have to declare the height and width as long or int32_t variables and then read 4 bytes at each offset, while using bit shifting to assemble the bytes into one number and remembering that it's little endian. Something like this:

long width |= bmpImage.read();
width |= (long)(bmpImage.read()) << 8;
width |= (long)(bmpImage.read()) << 16;
width |= (long)(bmpImage.read()) << 24;

Repeat this to get the height but use abs() to get the absolute value since its usually negative. You can also check for a colour table; usually the ColorDepth field is a good indicator (offset 28); if its less than 16, then you should expect a colour table. But if you know this already, you can skip it and get to the start offset, usually 0x36. Your current code doesnt take the padding into account (even though your image doesn't have any padding) and it also isnt using the actual image width in bytes. To get this width, assuming the BMP uses RGB888 24 bits per pixel:

width = ((width * 24) + 31) / 32;
width *= 4;

With this, you can now read each row, though its usually arranged bottom-up. However, you arent writing properly to the file. You should use textfile.print() and not write(); the former will write the decimal value of each byte to your file while the latter will write the exact un-encoded byte to the text file, which would result in a lot of gibberish when you try to display the file's contents, say on Notepad, since the range of printable characters (under default ASCII encoding) is between 32 - 127 and your image probably has lots of bytes outside that range.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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