c++ loading .bmp - encountering odd issue

Associate
Joined
10 Oct 2008
Posts
353
Location
Dundee
Hi, I am trying to load a .bmp file. My intention is to convert the pixels through some colour manipulation (average of bgr components probably) into greyscale and save them into a custom format which I will load into my OpenGL app as a heightmap to render terrain.

I made a thread over at gamedev.net (site might not respond, it seems to get DDOSd all the time or something :mad:) but it seems to have gone stale so I need some help from the good people here :D

Heres the full c++ code (interesting bit extracted below):
Code:
#include <stdint.h>
#include <iostream>

#define DEBUG

#ifdef DEBUG
  #define DEBUG_INPIXELS
#endif

typedef struct fileHeaderMagic{
  unsigned char magic[2];
} fileHeaderMagic;
 
typedef struct fileHeader{
  uint32_t filesz;
  uint16_t creator1;
  uint16_t creator2;
  uint32_t bmp_offset;
} fileHeader;

typedef struct fileBmpHeader{
  uint32_t header_sz;
  uint32_t width;
  uint32_t height;
  uint16_t nplanes;
  uint16_t bitspp;
  uint32_t compress_type;
  uint32_t bmp_bytesz;
  uint32_t hres;
  uint32_t vres;
  uint32_t ncolors;
  uint32_t nimpcolors;
} fileBmpHeader;

//24 bpp colour format
typedef struct col24{
  unsigned char b;
  unsigned char g;
  unsigned char r;
} col24;

using namespace std;

bool convert(char* file){
  //make the image object
  FILE * image;
  //open the image file for reading
  image = fopen(file,"r");
  //check that it was opened
  if(image == NULL){
    cout << "could not open file";
    return false;
  }
  
  //make the magic, file header and bitmap header
  fileHeaderMagic magic;
  fileHeader header;
  fileBmpHeader bmpHeader;

  //read the magic
  fread(&magic,sizeof(magic),1,image);
  //check the magic
  if(magic.magic[0] != char(66) || magic.magic[1] != char(77)){
    cout << "file is not a Windows bitmap";
    return false;
  }
  
  //read the header
  fread(&header,sizeof(header),1,image);
  
  //read the bitmap header
  fread(&bmpHeader,sizeof(bmpHeader),1,image);
  //check header attributes
  if(bmpHeader.header_sz != 40){
    cout << "unexpected header size";
    return false;
  }
  if(bmpHeader.nplanes != 1){
    cout << "unexpected colour planes";
    return false;
  }
  if(bmpHeader.width < 1 || bmpHeader.height < 1){
    cout << "bitmap has an invalid dimension";
    return false;
  }
  if(bmpHeader.bitspp != 24){
    cout << "bitmap must be 24 bits per pixel";
    return false;
  }
  if(bmpHeader.compress_type != 0){
    cout << "bitmap must be uncompressed";
    return false;
  }
  if(bmpHeader.ncolors != 0){
    cout << "bitmap has an invalid number of colours (" << 
    int(bmpHeader.ncolors) << ")";
    return false;
  }
  
  //calculate bytes per row
  int bytesPerRow = bmpHeader.bmp_bytesz/bmpHeader.height;
  #ifdef DEBUG
  cout << "bytesPerRow: " << bytesPerRow << endl; 
  #endif
  //calculate padding size
  int padding = bytesPerRow - bmpHeader.width*3; //byte per row - byte of pixels
  #ifdef DEBUG
  cout << "padding: " << padding << endl; 
  #endif
  char scrap[padding]; //bytes of padding scrap
  //make an array of 24 bit pixels
  col24 inPixels[bmpHeader.width*bmpHeader.height];
  #ifdef DEBUG
  cout << bmpHeader.width*bmpHeader.height << " pixels" << endl;
  cout << sizeof(col24) << " bytes per pixel" << endl;
  cout << sizeof(inPixels) << " bytes in inPixels array" << endl;
  cout << bmpHeader.width*bmpHeader.height*sizeof(col24) << 
  " bytes (expected)" << endl;
  system("pause");
  #endif
  //current pixel
  int currentPixel = 0;
  //loop through each row bottom to top
  for(int row = bmpHeader.height - 1;row >= 0;row --){
    #ifdef DEBUG
    cout << "row " << row << endl;;
    #endif
    //loop through each column
    for(int col = 0;col < bmpHeader.width;col ++){
      #ifdef DEBUG_INPIXELS
      cout << "currentPixel: " << currentPixel << endl;
      #endif
      fread(&inPixels[currentPixel],3,1,image); //read 3 byte as a colour
      #ifdef DEBUG_INPIXELS
      
      if(inPixels[currentPixel].b == 0 && inPixels[currentPixel].g == 0 && 
      inPixels[currentPixel].r == 0) system("pause");
      
      cout << int(inPixels[currentPixel].b) << ",";
      cout << int(inPixels[currentPixel].g) << ",";
      cout << int(inPixels[currentPixel].r) << " ";
      #endif
      currentPixel ++; //move to the next pixel
    }
    fread(&scrap,padding,1,image); //read the padding as scrap
    #ifdef DEBUG
    cout << endl;
    #endif
  }
  
  #ifdef DEBUG
  system("pause");
  #endif
  
  return true;
}

//application entry point
int main(int argc, char **argv, char **envp){
  char * open; //path to file to convert
  bool err; //if an error has occurred
  if(argc >= 2){
    open = argv[1];
    err = !convert(open);
  } else {
    #ifdef DEBUG
    err = !convert("image.bmp");
    #endif
    #ifndef DEBUG
    cout << "please specify a file";
    err = true;
    #endif
  }
  
  if(err){
    cout << endl;
    system("pause");
  }
  
  /*
  //bits per pixel
  uint16_t bpp = 24;
  //file dimensions
  uint32_t width = 64,height = 64,
  //calculate some required information
  pixels = width*height,dataSize;
  
  //make the "magic", file header and bitmap header
  fileHeaderMagic file_headerMagic;
  fileHeader file_header;
  bmpHeader file_bmpHeader;
  
  //make a colour in rbg 24 bit format
  colour bmpCol;
  bmpCol.r = 0xff;
  bmpCol.g = 0;
  bmpCol.b = 0xff;
  
  int16_t padding;
  //calculate the size in bytes of the pixels in each row
  int16_t pixelRowBytes = width*bpp/8;
  cout << "pixelRowBytes: " << pixelRowBytes << endl;
  //calculate the padding at the end of a row
  if(pixelRowBytes%4 == 0){ //if no padding is required
    cout << "no padding required" << endl;
    padding = 0;
  } else {
    cout << "needs padding" << endl;
    padding = 0; //bleh
  }
  //calculate the width of a row in bytes
  uint16_t rowBytes = pixelRowBytes + padding;
  cout << "rowBytes: "  << rowBytes << endl;
  
  //which part of the colour is being written (bgr)
  uint16_t colourPart = 0;
  //calculate the data size
  dataSize = height*rowBytes;
  cout << "dataSize: " << dataSize << endl;
  //make the bitmap data array
  unsigned char bmpData[dataSize];
  //fill the data
  for(uint16_t row = height;row > 0;row --){ //loop rows, bottom to top order
    for(uint16_t col = 0;col < pixelRowBytes;col ++){ //each column with pixels
      switch(colourPart){
        case 0:
          bmpData[row*pixelRowBytes-pixelRowBytes+col] = bmpCol.b; //fill the blue byte
          colourPart = 1;
          break;
        case 1:
          bmpData[row*pixelRowBytes-pixelRowBytes+col] = bmpCol.g; //fill the green byte
          colourPart = 2;
          break;
        case 2:
          bmpData[row*pixelRowBytes-pixelRowBytes+col] = bmpCol.r; //fill the red byte
          colourPart = 0;
          break;
      }
    }
  }
  
  //populate the "magic"
  file_headerMagic.magic[0] = 0x42; //B
  file_headerMagic.magic[1] = 0x4D; //M
  
  //populate the file header
  file_header.filesz = sizeof(fileHeaderMagic)+sizeof(fileHeader)+
  sizeof(bmpHeader)+dataSize;
  file_header.creator1 = 0;
  file_header.creator2 = 0;
  file_header.bmp_offset = sizeof(fileHeaderMagic)+
  sizeof(fileHeader)+sizeof(bmpHeader);
  
  //populate the bitmap header
  file_bmpHeader.header_sz = sizeof(bmpHeader);
  file_bmpHeader.width = width;
  file_bmpHeader.height = height;
  file_bmpHeader.nplanes = 1;
  file_bmpHeader.bitspp = bpp;
  file_bmpHeader.compress_type = 0; //uncompressed
  file_bmpHeader.bmp_bytesz = dataSize; //size in bytes of the data
  file_bmpHeader.hres = 2835; //pixels per meter, wut
  file_bmpHeader.vres = 2835;
  file_bmpHeader.ncolors = 0; //no colours in palette
  file_bmpHeader.nimpcolors = 0;
  
  //handle for the image file
  FILE * image;
  //path for the image file
  image = fopen("image.bmp","w");
  
  //write the "magic"
  fwrite(&file_headerMagic,1,sizeof(fileHeaderMagic),image);
  //write the file header
  fwrite(&file_header,1,sizeof(fileHeader),image);
  //write the bitmap header
  fwrite(&file_bmpHeader,1,sizeof(bmpHeader),image);
  //write the bitmap data
  fwrite(bmpData,1,dataSize,image);
  //done with the image
  fclose(image);
  */
  
  //exit application
  return 0;
}

This bit (highlighted as php :D) is doing the work, and I think may contain the problem:
PHP:
  int bytesPerRow = bmpHeader.bmp_bytesz/bmpHeader.height;
  #ifdef DEBUG
  cout << "bytesPerRow: " << bytesPerRow << endl; 
  #endif
  //calculate padding size
  int padding = bytesPerRow - bmpHeader.width*3; //byte per row - byte of pixels
  #ifdef DEBUG
  cout << "padding: " << padding << endl; 
  #endif
  char scrap[padding]; //bytes of padding scrap
  //make an array of 24 bit pixels
  col24 inPixels[bmpHeader.width*bmpHeader.height];
  #ifdef DEBUG
  cout << bmpHeader.width*bmpHeader.height << " pixels" << endl;
  cout << sizeof(col24) << " bytes per pixel" << endl;
  cout << sizeof(inPixels) << " bytes in inPixels array" << endl;
  cout << bmpHeader.width*bmpHeader.height*sizeof(col24) << 
  " bytes (expected)" << endl;
  #endif
  //current pixel
  int currentPixel = 0;
  //loop through each row bottom to top
  for(int row = bmpHeader.height - 1;row >= 0;row --){
    #ifdef DEBUG
    cout << "row " << row << endl;;
    #endif
    //loop through each column
    for(int col = 0;col < bmpHeader.width;col ++){
      #ifdef DEBUG_INPIXELS
      cout << "currentPixel: " << currentPixel << endl;
      #endif
      fread(&inPixels[currentPixel],3,1,image); //read 3 byte as a colour
      #ifdef DEBUG_INPIXELS
      cout << int(inPixels[currentPixel].b) << ",";
      cout << int(inPixels[currentPixel].g) << ",";
      cout << int(inPixels[currentPixel].r) << " ";
      #endif
      currentPixel ++; //move to the next pixel
    }
    fread(&scrap,padding,1,image); //read the padding as scrap
    #ifdef DEBUG
    cout << endl;
    #endif
  }

My reply in that thread on gamedev.net, extracted (this is where things get strange):
-------------------------------------------------
ok, I changed the sample image and now it gets so far through then thinks every pixel after then is just 0,0,0

with an image of only one colour (i dunno, 200,10,44) it works. But if there are a bunch of different colours present in the image it starts picking up everything as 0,0,0 after a certain point :S What...
This image makes it happen (programmer art yay):
whatlol.png

Whereas if the image had only the yellow background and none of the coloured bars, it goes through the whole thing without a fuss - picking up the yellow all the way. Considering I am doing nothing with palettes or anything like that, how is this even possible?

edit:
Look at this:
http://www.bozebo.com/images/ubbuh.png (changed to link because it might be too wide for OCUK)
Always at the same pixel...

And look, the following image works fine without it ever reporting 0,0,0
worksfine.bmp

Image dimensions: 240x175
If I make the 240x175 image look like this instead, it starts always reporting 0,0,0 at pixel 874.

I am confused.
 
Last edited:

Couldn't you fire up a debugger and step through it to see what values cause it to go all **** up? Also are you using a framework? I would assume that a game dev framework would handle asset loading with a lot less hassle ;) and theres no point reinventing the wheel.

Oh and some of your images are Png's and others bmp's i don't know if that could be causing some confusion or you just uploaded them like that for fun :confused:
 
Debugger tells me nothing I don't aready know. For some impossible reason fread(&inPixels[currentPixel],3,1,image); starts to only read 0x000000 after a certain value of currentPixel, that changes depending on the image - if I change 1 pixel in the image (located at a place before it started reporting 0) to a different colour then the value of currentPixel that starts bugging up changes for that bitmap :S

I tried reading some deeper in pixels manually from the bitmap file and they were fine :S it is only that loop that decides to pick up 0 values. I am beginning to think it is a cache thing.

No no framework, I need to get a base understanding of some of the low level things first, after I get some terrain working with distance based chunk LOD I am going to put a plane of water in and have a play with shaders, covering a few different bases a bit here.

Oh er, don't worry about the png/bmp. I converted the first one to png to upload to the web by mistake, the other png is a screenshot.

edit:
I suppose I could read it 1 line at a time. But that wouldn't explain the weirdness that is happening.
I might try and compile it with another compiler now, see if it still bugs.

edit:
OK, get the problem in my other compiler too.

I added checks for EOF and failure to read pixel values.
Take a look:
PHP:
  //loop through each row bottom to top
  for(int row = bmpHeader.height - 1;row >= 0;row --){
    #ifdef DEBUG
    cout << "row " << row << endl;;
    #endif
    //loop through each column
    for(int col = 0;col < bmpHeader.width;col ++){
      #ifdef DEBUG_INPIXELS
      cout << "currentPixel: " << currentPixel << endl;
      #endif
      //read the next 3 bytes into the current target pixel
      pixelsRead = fread(&inPixels[currentPixel],3,1,image);
      if(pixelsRead == 0){
        cout << "unable to read pixel " << currentPixel << " from file";
        if(feof(image) != 0){
          cout << ", end of file encountered";
          return false;
        }
        return false;
      }
      #ifdef DEBUG_INPIXELS
      if(inPixels[currentPixel].b == 0 && inPixels[currentPixel].g == 0 && 
      inPixels[currentPixel].r == 0) system("pause");
      
      cout << int(inPixels[currentPixel].b) << ",";
      cout << int(inPixels[currentPixel].g) << ",";
      cout << int(inPixels[currentPixel].r) << " ";
      #endif
      currentPixel ++; //move to the next pixel
      //check that EOF was not encountered
      if(feof(image) != 0){
        cout << "unexpectedly encountered end of file";
        return false;
      }
    }
    //skip the padding
    if(padding != 0 && fseek(image,padding,SEEK_CUR) == 0){
      cout << "failed to skip padding";
      return false;
    }
    #ifdef DEBUG
    cout << endl;
    #endif
  }
It is halting with the message "unable to read pixel 873, end of file encountered"
Well that explains the problem, but it shouldn't be possible for it to hit the end of the file. Why do bitmaps with simple colours work but not my querky ones with the vertical lines through them?

It's really confusing me :S

edit:
I am going to make it read a whole line at a time instead of mess about per-pixel. I was doing it like that so I could process the pixel on the fly and write it to the output but the bug fix takes priority over that.
 
Last edited:
I dled it and had a read through (i am no C++ developer, but this is really just C which i knowish).

Change your:
PHP:
 fopen(file,"r");


PHP:
fopen(file,"rb");

and all should be good. b for binary :) It reads both files that you said should fail just fine.
 
I dled it and had a read through (i am no C++ developer, but this is really just C which i knowish).

Change your:
PHP:
 fopen(file,"r");


PHP:
fopen(file,"rb");

and all should be good. b for binary :) It reads both files that you said should fail just fine.

:O
HOW did i miss that?! >_<

Wow, that was stupid of me. I have been stressing over this for days. I never even imagined that I was not actually loading it in binary >_<. Gah!

It works fine now :D I have since made adaptions to the code in other, better, ways too.

Thanks so much.
 
:O
HOW did i miss that?! >_<

Wow, that was stupid of me. I have been stressing over this for days. I never even imagined that I was not actually loading it in binary >_<. Gah!

It works fine now :D I have since made adaptions to the code in other, better, ways too.

Thanks so much.

haha errors like this are always something silly i find. As the chances are you will assume that something like that you would stick in without thinking so naturally you don't pay much attention to it when you get some error like this.
 
Back
Top Bottom