Reputation: 2118
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
Upvotes: 2
Views: 1782
Reputation: 2118
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!
Upvotes: 2
Reputation: 23500
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.
Upvotes: 1
Reputation: 4280
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
Upvotes: 2