2

I'm trying to read a .bmp file with c++ and save the grey values (average over RGB values) normalized into a vector under Ubuntu 14.04. Somehow the values of the vector end up completely wrong. Can you imagine why?

std::vector<double> readBMP(const char* filename, int* width, int* height){
    std::vector<double> bmp;
    FILE* f = fopen(filename, "rb");

    if(f == NULL){
        std::cerr << "file not found!" << std::endl;
        std::vector<double> empty;
        width = NULL;
        height = NULL;
        return empty;
    }

    unsigned char info[54];
    fread(info, sizeof(unsigned char), 54, f); // read the 54-byte header

    // extract image height and width from header
    *width = *(int*)&info[18];
   *height = *(int*)&info[22];
    int data_offset = *(int*)(&info[0x0A]);
    fseek(f, (long int)(data_offset - 54), SEEK_CUR);

    int row_padded = (*width*3 + 3) & (~3);
    unsigned char* data = new unsigned char[row_padded];
    unsigned char tmp;

    for(int i = 0; i < *height; i++)
    {
        fread(data, sizeof(unsigned char), row_padded, f);
        for(int j = 0; j < *width*3; j += 3)
        {
            // Convert (B, G, R) to (R, G, B)
            tmp = data[j];
            data[j] = data[j+2];
            data[j+2] = tmp;
            bmp.push_back(((double)data[j]+(double)data[j+1]+(double)data[j+2])/(3*255));

            std::cout << "R: "<< (int)data[j] << " G: " << (int)data[j+1]<< " B: " << (int)data[j+2]<< std::endl;
        }
    }
    return bmp;
}

I print the rgb values, and checked it with an example image, which has four pixels:

black | black | black
---------------------
grey  | grey  | grey
---------------------
white | white | white

The expected output should be (it's reversed):

R: 255 G: 255 B: 255
R: 255 G: 255 B: 255
R: 255 G: 255 B: 255
R: 128 G: 128 B: 128
R: 128 G: 128 B: 128
R: 128 G: 128 B: 128
R: 0 G: 0 B: 0
R: 0 G: 0 B: 0
R: 0 G: 0 B: 0

but it is:

R: 255 G: 255 B: 255
R: 255 G: 255 B: 255
R: 255 G: 255 B: 255
R: 128 G: 128 B: 255
R: 128 G: 255 B: 128
R: 255 G: 128 B: 128
R: 0 G: 0 B: 255
R: 0 G: 255 B: 0
R: 255 G: 0 B: 0

Note: The code is a modified version of an answer of this question: read pixel value in bmp file

Community
  • 1
  • 1
Natjo
  • 2,005
  • 29
  • 75
  • Does the size value match the width * height? (address 0x22 in the header). Also make sure that there's no offset to take into account (0x0A) in addition to the 54 bytes header. – Mr_Pouet Dec 31 '15 at 16:22
  • I check the width and height, it is read correctly. What kind of offset do you mean? – Natjo Dec 31 '15 at 16:29
  • 1
    Sometime the header is not standard and bigger than 54 bytes. If that's the case, you need to use `fseek` in order to move the cursor to the beginning of the data block. Follow up question, what OS are you using? – Mr_Pouet Dec 31 '15 at 16:33
  • I updated the question, I'm using Ubuntu 14.04. How can I see it the header is not standard? – Natjo Dec 31 '15 at 16:35
  • 1
    `int data_offset = *(int*)(&info[0x0A]); fseek(f, (long int)(data_offset - 54), SEEK_CUR);` – Mr_Pouet Dec 31 '15 at 16:37

3 Answers3

2

Depending on how it was encoded, your BMP header might not be standard and its size will be larger than 54 bytes. If that's the case, you need to use fseek in order to move the cursor to the beginning of the data block.

int data_offset = *(int*)(&info[0x0A]); 

if (data_offset > 54) {
    fseek(f, (long int)(data_offset - 54), SEEK_CUR);
} 

As pointed out by Brandon, your picture is encoded upside down as specified by the format spec (since you specified being on Ubuntu):

The pixel array is a block of 32-bit DWORDs, that describes the image pixel by pixel. Normally pixels are stored "upside-down" with respect to normal image raster scan order, starting in the lower left corner, going from left to right, and then row by row from the bottom to the top of the image.[5] Unless BITMAPCOREHEADER is used, uncompressed Windows bitmaps also can be stored from the top to bottom, when the Image Height value is negative.

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

Mr_Pouet
  • 4,061
  • 8
  • 36
  • 47
  • thanks, that solved the problem for 2x2, but now that I tried it with 3x3, it reads the wrong values again. although the first 3 pixels are right, afterwards there is an error – Natjo Dec 31 '15 at 19:39
2

Finally I got my code right, in case someone stumbles upon this question: My mistake was, that the image was in ABGR, I assumed BGR.

#include <cstdio>
#include <iostream>
#include <vector>

std::vector<double> readBMP(const char* filename, int* width, int* height){
    std::vector<double> bmp;
    FILE* f = fopen(filename, "rb");

    if(f == NULL){
        std::cerr << "file not found!" << std::endl;
        std::vector<double> empty;
        width = NULL;
        height = NULL;
        return empty;
    }

    unsigned char info[54];
    fread(info, sizeof(unsigned char), 54, f); // read the 54-byte header

    // extract image height and width from header
    *width = *(int*)&info[18];
    *height = *(int*)&info[22];

    int data_offset = *(int*)(&info[0x0A]);
    if(data_offset > 0)
        fseek(f, (long int)(data_offset - 53), SEEK_CUR);

    std::cout << "  Name: " << filename << std::endl;
    std::cout << " Width: " << *width << std::endl;
    std::cout << "Height: " << *height << std::endl;
    std::cout << "Offset: " << data_offset << std::endl;

    int row_padded = (*width*4 + 4) & (~4);
    unsigned char* data = new unsigned char[row_padded];
    //unsigned char tmp;

    for(int i = 0; i < *height; i++){
        fread(data, sizeof(unsigned char), row_padded, f);
        for(int j = 0; j < row_padded; j += 4)
        {
            // Convert (B, G, R) to (R, G, B)
            //tmp = data[j];
            //data[j] = data[j+2];
            //data[j+2] = tmp;
            bmp.push_back(((double)data[j+2]+(double)data[j+1]+(double)data[j+2])/(3*255));

            //std::cout << "R: "<< (int)data[j+1] << " G: " << (int)data[j+2]<< " B: " << (int)data[j+3]<< std::endl;
        }
    }
    free(data);

    //reverse order of the vector
    std::vector<double>bmp_final;
    std::cout << bmp.size() << std::endl;
    for(int i=*height-1; i>=0; --i){
        for(int j=0; j<*width; ++j){
            bmp_final.push_back(bmp.at(*width*i+j));
        }
    }

    return bmp_final;
}

Thanks to everyone for the help!

Natjo
  • 2,005
  • 29
  • 75
1

Stop hardcoding the offsets. The offset of the pixels are actually found at info[10] + (info[11] << 8) where info is the header.

#include <cstdio>
#include <cstdlib>
#include <cstdint>
#include <vector>

typedef struct bitmap
{
    unsigned short bpp;
    unsigned int width, height;
    std::vector<unsigned char> pixels;
} bitmap;

bool LoadBmp(const char *filepath, bitmap *bmp)
{
    FILE *f = fopen(filepath, "rb");

    if (f)
    {
        bmp->bpp = 0;
        bmp->width = 0;
        bmp->height = 0;
        bmp->pixels.clear();

        unsigned char info[54] = {0};
        fread(info, sizeof(unsigned char), 54, f);

        bmp->width = info[18] + (info[19] << 8); //Width
        bmp->height = info[22] + (info[23] << 8); //Height


        bmp->pixels.resize(((((bmp->width * bmp->height) + 31) & ~31) / 8) * bmp->height); //Size of the pixels in the bitmap.

        fseek(f, info[10] + (info[11] << 8), SEEK_SET); //Seek to Pixel Offset.

        fread(&bmp->pixels[0], sizeof(unsigned char), bmp->pixels.size(), f); //Read the pixels.
        fclose(f);

        //Do whatever with pixels.. Flip them.. Swap BGR to RGB, etc..
        return true;
    }

    return false;
}

Finally, your black pixels are below your white pixels because a Bitmap is stored Upside-Down. You have to flip it yourself or read bottom-up.

Brandon
  • 22,723
  • 11
  • 93
  • 186
  • as info[11] is of type char and therefor contains 8 bits, won't a shift by 8 bits (<<8) make the value of info[11] always to zero? – Natjo Dec 31 '15 at 19:35