2

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;
}
Sam
  • 1,509
  • 3
  • 19
  • 28
Elephantinc
  • 33
  • 1
  • 5
  • "I'm fairly new to C++ but have been using Python for a while." C++ is for all practical purposes nothing like Python. If you don't already have a good C++ book, consider getting one of the introductory C++ books from [The Definitive C++ Book Guide and List](http://stackoverflow.com/questions/388242/the-definitive-c++-book-guide-and-list). – James McNellis Jul 23 '10 at 22:02
  • I have one and have read it,I just thought my inexperience was worth mentioning as it may be the cause of some of my mistakes. – Elephantinc Jul 23 '10 at 22:06
  • @Elephantic: Great! I just wanted to be sure. :-) A lot of people try to learn without a good book, and it generally doesn't turn out well. – James McNellis Jul 23 '10 at 22:08
  • Don't use `
    ` or `` - select the code and press the `010` button or indent by at least 4 spaces.
    – Georg Fritzsche Jul 23 '10 at 22:09
  • Ah, ok. I was trying to press the button and then paste the code. Thank you for fixing it :) – Elephantinc Jul 23 '10 at 22:11

3 Answers3

3

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.

James McNellis
  • 348,265
  • 75
  • 913
  • 977
  • Ok thanks, I'll look in to that. But do you know how to solve my problem when passing the pixel array to setBitmapFileHeader? It's stopping me from compiling the program and I wasn't sure how to do it. – Elephantinc Jul 23 '10 at 22:14
  • @Elephantic: `renderSize` needs to be `const` in order to be used as the dimension of an array. `&pixelArray` yields a pointer to the array, not a pointer to the first element (the pointer values are the same, but their types are different). If you drop the `&`, it should solve your problem. Note that you also have an error in your size computation (you use `renderSize[1]` twice) and that accessing a two-dimensional array as if it were a one-dimensional array is technically not allowed. – James McNellis Jul 23 '10 at 22:18
  • Thank you :) The compile time error though is at the line: setBitmapFileHeader(fileHeader,informationHeader,pixelArray); returning: "error: conversion from 'pixel (*)[(((ling unsigned int)(((int)renderSize[1]) - 1)) + 1u)]' to non-scalar type 'pixel' requested" My problem is that I don't really know what to put for the type here: "void setBitmapFileHeader(bitmapFileHeader& fileHeader,bitmapInformationHeader& informationHeader,pixel pixelArray) {" I thought it would be pixel, but that doesn't work. – Elephantinc Jul 23 '10 at 22:29
  • @Elephantic: It needs to be a `pixel pixelArray[][renderSize[1]`. – James McNellis Jul 23 '10 at 22:46
  • I missed an `]` at the end of that last comment's code. Oops. – James McNellis Jul 24 '10 at 04:47
  • Sorry to bother you so much but I'm still struggling and I'm not sure what's wrong. Here's a screenshot of the .bmp in a hexadecimal editor: http://yfrog.com/fvpicture1ygp Any clues as to what's wrong? – Elephantinc Aug 06 '10 at 22:49
  • @Elephantic: I'm not really that familiar with the BMP file format, certainly not familiar enough to see what's wrong in a hex dump ;-). I'd compare your hex dump with a hex dump from a good .bmp file (i.e., one created from Paint or something like that). – James McNellis Aug 06 '10 at 22:59
  • Thank you for all your help, 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. I've compared it with an good .bmp and it should all be zerod. I can't explain where it's coming from. Any ideas? – Elephantinc Aug 08 '10 at 00:52
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.

Sniggerfardimungus
  • 11,583
  • 10
  • 52
  • 97
1

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

karlphillip
  • 92,053
  • 36
  • 243
  • 426