Tutorial :Difficulty writing a Bmp file in C++ manually (using Xcode)



Question:

I'm currently trying to create a ray tracer in C++ but I'm having difficulty writing the .bmp produced at the end. I'm determined to do it manually, so I can learn more about image files and writing them etc. But I'm having some difficulty. I'm fairly new to C++ but have been using Python for a while.

I'm almost there now, I just have one strange problem. Everything is correct up to mid way trough the important colours (which I set to 0), where all sorts of random characters spring up and this continues for the next few bytes, a couple of bytes in to the pixel data. Before and after that everything is fine, but I just can't explain it. My current code is in the edit:

Here is the hex:

http://yfrog.com/j3picture1equp

the problem area is highlighted

 #include <iostream>  #include <fstream>  #include <math.h>  using namespace std;  //-------------------------Object list  const int renderSize[2] = {254,254};  float sphere1Pos[3] = {0.0,0.0,0.0}; //first sphere at origin to make calculations easier  float sphere1Radius = 10.0;  float sphere1Colour= (255.0,0.0,0.0);   float light1Pos = (0.0,20.0,0.0); //Above sphere  float light1Intensity = 0.5;  // ------------------------        float dot(float* a,float* b); //Calculates the dot product    struct pixel {        unsigned char R;      unsigned char G;      unsigned char B;      };    //bmp--------------  struct bitmapMagicNumber {      unsigned char magicNumber[2];      };    struct bitmapFileHeader {      unsigned char fileSize;      short reserved1;      long reserved2;      short offset;      };    struct bitmapInformationHeader {      short headerSize;      short padding;      short width;      short height;       short planes;      short bitDepth;      short compression;      short imageSize;      short xPixelsPerMetre;      short yPixelsPerMetre;      short colours;      short importantColours;      };    void setBitmapMagicNumber(bitmapMagicNumber& magicNum){      magicNum.magicNumber[0] = 0x42;      magicNum.magicNumber[1] = 0x4D;      };    void setBitmapFileHeader(bitmapFileHeader& fileHeader,bitmapInformationHeader& informationHeader,pixel pixelArray) {      fileHeader.fileSize = 54 + sizeof(pixelArray);      fileHeader.reserved1 = 0;      fileHeader.reserved2 = 0;      fileHeader.offset = 54;      };    void setBitmapInformationHeader(bitmapInformationHeader& informationHeader){      informationHeader.headerSize = 40;      informationHeader.padding=0;      informationHeader.width = renderSize[0];      informationHeader.height = renderSize[1];      informationHeader.planes = 1;      informationHeader.bitDepth = 24;      informationHeader.compression = 0;      informationHeader.imageSize = 0;      informationHeader.xPixelsPerMetre = 0;      informationHeader.yPixelsPerMetre = 0 ;      informationHeader.colours = 0;      informationHeader.importantColours = 0;      };    void writeBitmap(bitmapMagicNumber& magicNum, bitmapFileHeader& fileHeader,bitmapInformationHeader& informationHeader,pixel pixelArray){      ofstream out("test.bmp",ios::out|ios::binary);        //file header      out.write((char*) &magicNum,2);      out.write((char*) &fileHeader.fileSize,sizeof(fileHeader.fileSize));      if (sizeof(fileHeader.fileSize)<3){          out.write((char*) &informationHeader.padding,1);          }      out.write((char*) &informationHeader.padding,1);      out.write((char*) &fileHeader.reserved1,2);      out.write((char*) &fileHeader.reserved2,2);      out.write((char*) &fileHeader.offset,sizeof(fileHeader.offset));      out.write((char*) &informationHeader.padding,1);      out.write((char*) &informationHeader.padding,1);          //information header      out.write((char*) &informationHeader.headerSize,sizeof(informationHeader.headerSize));      out.write((char*) &informationHeader.padding,1);      out.write((char*) &informationHeader.padding,1);            out.write((char*) &informationHeader.width,sizeof(informationHeader.width));      if (sizeof(informationHeader.width)<4){          out.write((char*) &informationHeader.padding,1);          }      if (sizeof(informationHeader.width)<3){          out.write((char*) &informationHeader.padding,1);          }      if (sizeof(informationHeader.width)<2){          out.write((char*) &informationHeader.padding,1);          }        out.write((char*) &informationHeader.height,sizeof(informationHeader.height));      if (sizeof(informationHeader.height)<4){          out.write((char*) &informationHeader.padding,1);          }      if (sizeof(informationHeader.height)<3){          out.write((char*) &informationHeader.padding,1);          }      if (sizeof(informationHeader.height)<2){          out.write((char*) &informationHeader.padding,1);          }           out.write((char*) &informationHeader.planes,sizeof(informationHeader.planes));      out.write((char*) &informationHeader.bitDepth,sizeof(informationHeader.bitDepth));      out.write((char*) &informationHeader.compression,4);      out.write((char*) &informationHeader.imageSize,4);      out.write((char*) &informationHeader.xPixelsPerMetre,4);      out.write((char*) &informationHeader.yPixelsPerMetre,4);      out.write((char*) &informationHeader.colours,4);      out.write((char*) &informationHeader.importantColours,4);        //pixel data      for (int y=0; y < renderSize[1]; y++) {          for (int x=0; x< renderSize[0]; x++) {              out.write((char*) &pixelArray[x][y],sizeof(pixel));          }      }        out.close();      }      // end bmp-----------    int main() {    pixel pixelArray[renderSize[0]][renderSize[1]];        for (int y=0; y < renderSize[1]; y++) {          for (int x=0; x< renderSize[0]; x++) {              float rayPos[3] = {x,y, -1000.0};              float rayDir[3] = {0.0,0.0,-1.0};                 bool intersect;                //for each object in scene, see if intersects. (for now there is only one object to make things easier)                //-------sphere ray intersection....              float distance[3];              distance[0]= rayPos[0]-sphere1Pos[0];              distance[1]= rayPos[1]-sphere1Pos[1];              distance[2]= rayPos[2]-sphere1Pos[2];              float a = dot(rayDir, rayDir);              float b = 2 * dot(rayDir, distance);              float c = dot(distance, distance) - (sphere1Radius * sphere1Radius);                float disc = b * b - 4 * a * c;                if (disc < 0)                  intersect=false;              else                  intersect=true;                  //--------------------                if (intersect==true){                  pixelArray[x][y].R = 0;                  pixelArray[x][y].G = 0;                  pixelArray[x][y].B = 0;                  }                else {                  pixelArray[x][y].R = 0;                  pixelArray[x][y].G = 0;                  pixelArray[x][y].B = 0;                  }                    // trace to lights (as long as another object is not in the way)             }        }      //write .bmp      bitmapMagicNumber magicNum;      bitmapFileHeader fileHeader;      bitmapInformationHeader informationHeader;        setBitmapMagicNumber(magicNum);      setBitmapFileHeader(fileHeader,informationHeader, pixelArray[renderSize[0]][renderSize[1]]);      setBitmapInformationHeader(informationHeader);        writeBitmap(magicNum,fileHeader,informationHeader, pixelArray[renderSize[0]][renderSize[1]]);  }    //calculate dot product  float dot(float* a,float* b)  {  float dp = 0.0;  for (int i=0;i<3;i++)      dp += a[i] * b[i];  return dp;  }  


Solution:1

While it may not be your only issue, you almost certainly have data alignment issues.

In your bitmapFileHeader, for example, assuming long has four-byte alignment and short has two-byte alignment, there will be two bytes of unnamed padding between magicNumber and fileSize (there are similar issues in most of the other data structures).

As a solution, you can represent the header and other structures as an array of char (which has no padding) and copy the relevant data into the correct locations in the array.

Your compiler might provide a way to "pack" the data structures so that they are unaligned, which can also solve your problem, but doing so is wholly unportable.


Solution:2

I've always done output as a .ppm. the format couldn't be any simpler:

FILE * out = fopen("out.ppm", "wb");  fprintf(out, "P6 %d %d 255\n", HEIGHT, WIDTH);    for(int i=0; i<HEIGHT; i++)    for(int j=0; j<WIDTH; j++)    {      color c = get_pixel_color(i,j)      putc(c.red, out);      putc(c.green, out);      putc(c.blue, out);    }    fclose(out);  

...where red, green, and blue are ints, but must take on values between 0 and 255. The 255 in the ppm header indicates that the max value of a color channel is 255.

Most image editors will read a .ppm: gimp, photoshop, etc. I've always loved it because I can remember the format off the top of my head and I don't have to write anything that isn't used... When I REALLY need a .bmp or a .jpg, etc., I use imagemagick convert to convert my ppm to that format.


Solution:3

This code have functions for reading/writting BMPs on Windows and some other platform. Take a look.


Note:If u also have question or solution just comment us below or mail us on toontricks1994@gmail.com
Previous
Next Post »