OpenGL BMP Loader.

Okay, so yesterday I posted to this thread, and someone help me out a lot with a few problems I had, however, I am still having issues with a BMP loader I have made. I will post the code for it, and what I see. Thank you for your time!!

Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
GLuint LoadTexture(const char * pic)
{
	double width;
	double height;

	int BytesofFile;

	GLuint Texture;
	BYTE * data;
	FILE * picfile;

	picfile = fopen(pic, "rb");
	if (picfile == NULL)
		return 0;

	BITMAPFILEHEADER MyHeaderFile;
	fread(&MyHeaderFile, sizeof(BITMAPFILEHEADER), 1, picfile);
	if (MyHeaderFile.bfType != 0x4D42)
		return 0;

	BITMAPINFOHEADER MyHeaderInfo;
	fread(&MyHeaderInfo, sizeof(BITMAPINFOHEADER), 1, picfile);

	width = MyHeaderInfo.biWidth;
	height = MyHeaderInfo.biHeight;

	BytesofFile = MyHeaderInfo.biBitCount;

	data = (BYTE *)malloc(width * height * BytesofFile);

	cout<<"bfSize: "<<MyHeaderFile.bfSize<<endl<<"bfOffBits: "<<MyHeaderFile.bfOffBits<<endl<<"biSize: "<<MyHeaderInfo.biSize<<endl<<"biBitCount: "<<MyHeaderInfo.biBitCount<<endl;
	
	system("PAUSE");

	cout<<endl;

	fseek(picfile, MyHeaderFile.bfOffBits, SEEK_SET);
	fread(data, width * height, BytesofFile, picfile);
	fclose(picfile);
	
    glGenTextures(1, &Texture);
	glBindTexture(GL_TEXTURE_2D,  Texture);

	glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
	glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
	glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_BGR, GL_UNSIGNED_BYTE, data);

	free(data);
	return Texture;
}


What I see (Both pictures are supposed to look like the one on the right):
http://tinypic.com/r/15kc41/6

- Kyle
Pretty much most of the stuff I mentioned in the other thread still applies.

1) You still are ignoring the bitmap pitch (rows are padded to 4-byte boundaries)
2) You still are ignoring the OpenGL limitation where textures must have a power-of-2 size.
3) biBitCount is the depth of pixels in bits.... not bytes. Divide it by 8 to get the number of bytes per pixel.
4) You still are not taking into consideration that bitmaps are stored bottom row first.


#2 is probably your biggest problem here.


EDIT:

Also, I think biOffBits might not be the offset from the start of the file, but might be the offset from after the BITMAPFILEHEADER. Don't know for sure -- you'll have to check the documentation.
Last edited on
I don't know how to solve any of these (Except 3).... Sorry, I'm relativity new to OpenGL (and it doesn't help that I'm 14). However, I should note that when I give it the number of bytes instead of bits, it puts acolor chart at the top of the picture.

- Kyle
Last edited on
These are not really OpenGL problems as much as they're data manipulation problems.

Anyways....

Bitmap pitch

The pitch is the number of bytes between rows. This has to be calculated, as it is not stored directly in the bitmap header.

The pitch of the bitmap is equal to round_up_to_4byte_boundary( bytes_per_pixel * pixel_width )

So if there are 3 bytes per pixel, and you have a width of 150... this means there are actually 452 bytes per row... not 450.

Your code will need to account for this by either making the rows wider... or skip bytes in the file between reading each row.


Bitmaps are upside-down

Normal bitmaps are stored with the bottom row first and the top row last. OpenGL textures (and pretty much every other kind of imaging standard everywhere) does it the exact opposite, with the top row first. This means you will need to flip the image somehow. Either after it's loaded, or do it while you're loading.


Note that this isn't always the case. You can tell if an image is upside-down or not by looking at the biHeight value in the header. If it's positive, the bitmap is upside-down. If it's negative, the bitmap is rightside-up.

OpenGL needs power of 2 sizes

The buffer you give to openGL might have to be larger than the image data you have. For example if your image is 160 x 100 pixels... this means the smallest texture that image will fit on is 256 x 128 (unless you break the image up and rearrange it -- but that's another topic I won't get into).




What you need to do

Basically... this code is wrong.
1
2
3
data = (BYTE *)malloc(width * height * BytesofFile);
//...
fread(data, width * height, BytesofFile, picfile);



I would recommend you do several things differently:

1) Get the width, height, and bytes-per-pixel (bpp) value from the header
2) Use the width and bpp values to calculate the pitch
3) Use the width and height to calculate how big of an OpenGL texture you'll need. Remember the texture dims must be >= the image dims... and the texture dims must each be an exact power of 2 (2, 4, 8, 16, 32, 64, 128, 256, 512, 1024)
4) Allocate a buffer big enough to hold the entire texture (ie: power of 2 texture dims). Probably a good idea to wipe it to transparent black as well because you probably will not set every pixel according to the bmp file contents, due to the necessary power-of-2 padding.
5) Start reading rows individually from the file, rather than trying to read the entire image in one read.
6) If the image needs to be vertically flipped (positive height), then you can flip it by reading rows into different areas in the buffer. For example if the image is 10 pixels tall, you can read the first row in the file into row 9 of your texture buffer. 2nd row in the file goes in row 8 of your texture, etc, etc.
Okay... Thanks, but I still have a few questions, that I'll match to your numbers:

1. Don't I already do this?
2. Once I calculate the pitch, what do I do with it?
3. The OpenGL dimensions being the shape I apply the picture to to?
4. Huh?
5. How? (Obviously a while/for loop, but still...)
6. Store the image data in a multi-dimensional list?

- Kyle
1. Yes you do. Sorry for the confusion.

2. Then you know how many bytes you have to read from the file in order to get 1 row of pixel data.

3 & 4. There are 2 different images here. One is the source image (the bitmap file). This has its own width/height. The other image is the destination image (the OpenGL texture). This image must have dims that are a power of 2.

You will be creating a buffer (a chunk of memory: ie, that chunk you are malloc'ing). This buffer will be the OpenGL texture. This means you must create that buffer with the power-of-2 dimensions.

After you malloc the buffer, you will want to zero it. Since it will be larger than the source image, not all pixels will be drawn to it, and you don't want garbage pixels hanging around. So filling the buffer with zero up front ensures that everything is wiped clean to black.

5 & 6. You need to know how to get to any individual row in your malloc'd buffer. This is very easy:

1
2
3
4
5
6
7
BYTE* row0 = buffer;
BYTE* row1 = buffer + (OpenGL_width * bytes_per_pixel);
BYTE* row2 =  buffer + (OpenGL_width * bytes_per_pixel * 2);

// or...
BYTE* rowX = buffer + (OpenGL_width * bytes_per_pixel * X);
// where 'X' is the desired row number 


As you are loading the file, do a for loop and read 1 row's worth of data (ie: read 'pitch' bytes) into the desired row number (use above math to get the buffer to the desired row)

There is no mulitdimentional list. You're just working with a big buffer.
Sorry. School has prevented me from doing much coding, at all.

Okay, so does the memory I allocate to the heap have to be a power of two, or the shape I apply them to have to be? And would I detect how big it is by reading the width and height, then having a bunch of if statements comparing the texture to larger and larger, and use the first one that is larger? Or could I just make a HUGE (2048) memory block? - Lazy

As for reading in row by row, how do I know where to start reading in the file? After that, I could do a
1
2
3
4
5
6
7
8
9
10
11
12
for (int i = 0; i < Height; i ++)
{
     for (int b = 0; b < pitch; b ++)
     {
           MagicalMagicStuffThatIWillFigureOutLater();
     }

     for (int r = 0; r < width - pitch; r++)
     {
          //Fill  Buffer with 0?  I want it to be transparent....
     }
}

Correct?

Plus a random question, I currently read the texture every loop. Can I just save the texture number that it is (the one I bind to) and just do it that way?

And one last thing, how do I make the picture so that around the image it is see through, such as not seeing white corners around a red ball?

Thank you again,
- Kyle
Last edited on
Okay, so does the memory I allocate to the heap have to be a power of two, or the shape I apply them to have to be?


When you create an OpenGL texture -- the width/height you specify must be powers of 2. So I guess that means "the shape you apply them to".

And would I detect how big it is by reading the width and height, then having a bunch of if statements comparing the texture to larger and larger, and use the first one that is larger? Or could I just make a HUGE (2048) memory block?


Try to keep your textures under 1024x1024 as iirc that is a maximum texture size on some systems.... but I don't know if that has changed or not.

Anyway finding the nearest power-of-2 size is pretty simple... just have a loop where you keep multiplying a value by 2 until it's >= the necessary size.

As for reading in row by row, how do I know where to start reading in the file?


Not sure I understand this question. You start reading the file where the pixel data starts in the file. I thought you had already figured this part out? IIRC there's an 'offset' in the file header that tells you where the pixel data starts... but I don't recall where it is or how to use it. I'd have to look up the bmp file specs again.


for (int r = 0; r < width - pitch; r++)
{
//Fill Buffer with 0? I want it to be transparent....
}


This is one way to do it. Personally I would fill the buffer with 0 all at once before you start reading pixel data. Then you start with a fully transparent buffer and can just read the pixel data on top of it, not worrying about the parts that the pixel data doesn't fill.

If that makes sense...

Plus a random question, I currently read the texture every loop. Can I just save the texture number that it is (the one I bind to) and just do it that way?


This is very bad.

You probably should be loading the texture exactly once. Once it's loaded it stays in memory until you destroy it. Therefore you do not have to keep loading it over and over (and you shouldn't!).

Loading is a very slow process, so you don't want to do it repeatedly.

Once the texture is loaded the first time, you can just bind to it and use it for drawing as many times as you need.

And one last thing, how do I make the picture so that around the image it is see through, such as not seeing white corners around a red ball?


You will need to do a few things:

1) Enable alpha blending (this requires 2 or 3 function calls in OpenGL, but I can't remember exactly what they are... glEnable(GL_ALPHA_BLEND); I think... and glBlendFunc( xx, xx ); is the other one maybe... look it up)

2) Your texture will need to have an alpha channel. This means you'll probably want to create a 32-bit ARGB texture instead of a 24-bit RGB texture. The alpha channel specifies the 'opacity' of a pixel. 0=transparent, FF=fully opaque.

3) Your loader will have to read individual pixels from the file and convert them from 24-bit (as stored in your bitmap) to 32-bit (as needed by your OpenGL texture), assigning an appropriate alpha value to them. This means you can no longer load the image row at a time, but instead must load it pixel at a time.


It makes it more complicated for sure. I would recommend not worrying about adding the alpha channel until you have non-alpha texture loading working first.
Okay, I finally got some time to code. However, I don't know if it's pure exhaustion, or idiocy, but I don't know what to do. I have the size of the buffer as a ^2, or whatever you said it needs to be, and not reading in the texture every frame, but now I'm confused about how I read the individual bytes/bits (it's all turning to mush in my mind)... However, I'm going to post my current texture loader, and I would love for some more help. As always, thank you.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
GLuint LoadTexture(const char * pic)
{
	double width;
	double height;
	double pitch;

	int BytesofFile;
	int Pitch;

	GLuint Texture;
	BYTE * data;
	FILE * picfile;

	picfile = fopen(pic, "rb");
	if (picfile == NULL)
		return 0;

	BITMAPFILEHEADER MyHeaderFile;
	fread(&MyHeaderFile, sizeof(BITMAPFILEHEADER), 1, picfile);
	if (MyHeaderFile.bfType != 0x4D42)
		return 0;

	BITMAPINFOHEADER MyHeaderInfo;
	fread(&MyHeaderInfo, sizeof(BITMAPINFOHEADER), 1, picfile);

	if (MyHeaderInfo.biWidth < 1024)
	{
		width = 1024;
	}
	if (MyHeaderInfo.biHeight < 1024)
	{
		width = 1024;
	}

	if (MyHeaderInfo.biWidth < 512)
	{
		width = 512;
	}
	if (MyHeaderInfo.biHeight < 512)
	{
		width = 512;
	}
	
	if (MyHeaderInfo.biWidth < 256)
	{
		width = 256;
	}
	if (MyHeaderInfo.biHeight < 256)
	{
		width = 256;
	}
	
	if (MyHeaderInfo.biWidth < 128)
	{
		width = 128;
	}
	if (MyHeaderInfo.biHeight < 128)
	{
		width = 128;
	}

	if (MyHeaderInfo.biWidth < 64)
	{
		width = 64;
	}
	if (MyHeaderInfo.biHeight < 64)
	{
		width = 64;
	}
	
	if (MyHeaderInfo.biWidth < 32)
	{
		width = 32;
	}
	if (MyHeaderInfo.biHeight < 32)
	{
		width = 32;
	}

	if (MyHeaderInfo.biWidth < 16)
	{
		width = 16;
	}
	if (MyHeaderInfo.biHeight < 16)
	{
		width = 16;
	}
	
	if (MyHeaderInfo.biWidth < 8)
	{
		width = 8;
	}
	if (MyHeaderInfo.biHeight < 8)
	{
		width = 8;
	}

	if (MyHeaderInfo.biWidth < 4)
	{
		width = 4;
	}
	if (MyHeaderInfo.biHeight < 4)
	{
		width = 4;
	}

	if (MyHeaderInfo.biWidth < 2)
	{
		width = 2;
	}
	if (MyHeaderInfo.biHeight < 2)
	{
		width = 2;
	}

	BytesofFile = MyHeaderInfo.biBitCount;

	data = (BYTE *)malloc(width * height * BytesofFile);

	fseek(picfile, MyHeaderFile.bfOffBits, SEEK_SET);

	Pitch = ((BiBitCount * 8) * (Width));
	for (int i = 0; i < height; i++)
	{
		for (int b = 0; b < Pitch; b++)
		{
			data[i][b] = /////AHHH!! I'M LOST!  And now I can run my program. :(
		}
	}

	//fread(data, width * height, BytesofFile, picfile);
	fclose(picfile);
	
        glGenTextures(1, &Texture);
	glBindTexture(GL_TEXTURE_2D,  Texture);

	glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
	glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
	glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_BGR, GL_UNSIGNED_BYTE, data);

	free(data);
	return Texture;
}


- Kyle
I don't think I can explain it in any more detail than I already have. The only thing I could really do is write the code and show it to you, but I'm hesitant to do that because I don't think that's what you want. (am I wrong?)

Do you have a hex editor? Try opening a simple bmp file in the hex editor and examining it that way. That way you can see how the image is stored... which might give you an idea of how to manipulate it.


PS: you're calculating the pitch incorrectly. A 24-bit bitmap with a width of 10 will have a pitch of 32 (3 bytes per pixel * 10 pixels = 30, pad to 32).

But by your formula:
Pitch = ((BiBitCount * 8) * (Width));
Bitcount * 8 * Width = 24 * 8 * 10 = 1920 which is crazy wrong.




EDIT:

I was bored... so here is some completely untested code. You'll have to plug in some details, but it's mostly commented. Feel free to ask questions if parts of it aren't clear:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
int srcwidth = /*get width from header*/;
int srcheight = /*get height from header*/;
int srcbitsperpix = 24;  // this code assumes a 24-bit bitmap... but confirm that number with what the header says

bool inverty = true; // true if bitmap is stored "upside-down".  true by default in .bmp's
if(srcheight < 0)  // unless height is negative
{
    srcheight = -srcheight;
    inverty = false; // then it's false
}

int srcpitch = srcbitsperpix * srcwidth / 8;   // calculate the number of bytes of pixel data per row
srcpitch = (srcpitch + 3) & ~3;                // round up to multiple of 4.  That's the pitch.



int dstwidth = 2;  // width of opengl texture
int dstheight = 2; // height of gl texture

// gl dims must be larger than bitmap dims, and must be power of 2
//  so keep doubling them until they are large enough to hold the entire bitmap
while(dstwidth < srcwidth)      dstwidth *= 2;      // potential for infinite loops here if the src dims are stupid high 
while(dstheight < srcheight)    dstheight *= 2;     //   (above 0x40000000), so maybe check the dims and see if they're
                                                    //   unreasonable before doing this.

// create a buffer for the gl texture.  We want 4 bytes per pixel (32-bit texture)
uint8_t* const dstbuffer = new uint8_t[4 * dstwidth * dstheight];   // 'const' to prevent acidentally doing pointer math on this pointer

// zero the buffer so that pixels not filled by the bmp will be transparent black
std::fill_n( dstbuffer, 4 * dstwidth * dstheight, 0 );

// the destination pitch is much simpler...  no extra padding:
int dstpitch = dstwidth * 4;



////////////////////////////////
//  so now we know the source bmp dims and the source pitch
//  and we have the destination buffer, destination dims/pitch
//
//  to clarify:  'src' = the .bmp file
//               'dst' = the open gl texture
//
//  to keep this example as simple as possible, let's make an intermediate buffer to hold
//  one row of source pixel data
uint8_t* const srcrow = new uint8_t[ srcpitch ]; // 'const' to prevent pointer math on this buffer
uint8_t* dstrow = dstbuffer;  // our dstrow can just be our destination buffer (this points to the first row of pixel data)
int dst_bytestonextrow = dstpitch;  // the number of bytes between 'dstrow' and the next row of pixel data in the dstbuffer.
                                    //   this is what the pitch represents.


if(inverty)  // ... but if the image is stored upside-down...
{
    // write the first row of pixel data to rown H-1 of the destination buffer.
    //   that is... if the bitmap has a height of 10, and it's stored upside down
    //   the first row of pixel data in the source file is actually row 9
    dstrow = dstbuffer + ((srcheight-1) * dstpitch);

    // then each row after that is the previous row.... so adjust 'dst_bytestonextrow' so we move to previous rows
    dst_bytestonextrow = -dstpitch; // <- by making it the negative pitch
}

//////////////////////////////////
// start reading rows
for( int y = 0; y < srcheight; ++y )
{
    // get a row of src pixel data from the file
    ReadOneRowOfPixelDataFromBitmapFile( srcrow, srcpitch );  //read 'srcpitch' bytes into buffer 'srcrow'

    uint8_t* src = srcrow;  // our 'src' pointer, which points to individual pixels
    uint8_t* dst = dstrow;  // our 'dst' pointer, which points to individual pixels

    for( int x = 0; x < srcwidth; ++x )
    {
        // IIRC, bitmaps are stored in BGR order, which means:
        //   src[0] is the blue
        //   src[1] is the green
        //   src[2] is the red
        // also IIRC, GL textures are configurable in which order you list the components.  I'm going to go with the standard ARGB, so
        //   dst[0] is alpha
        //   dst[1] is red
        //   dst[2] is green
        //   dst[3] is blue

        if( /* you want this pixel to be transparent */ )
        {
            dst[0] = 0;  // replace this pixel with transparent (A=0)
            dst[1] = 0;  //    black (RGB=0)
            dst[2] = 0;
            dst[3] = 0;
        }
        else  // otherwise we want an opaque pixel
        {
            dst[0] = 0xFF;    // give it full alpha to make it opaque
            dst[1] = src[2];  // R component
            dst[2] = src[1];  // G component
            dst[3] = src[0];  // B component
        }

        // done loading this pixel... move our pointers to point to the next pixel in the row
        dst += 4;  // 4 bytes per pixel in the dst buffer
        src += 3;  // 3 bytes per pixel in the src buffer
    }

    // now we've completed reading a full row of pixels.  adjust our 'dstrow' pointer so it points
    //  to the next row in the destination buffer
    dstrow += dst_bytestonextrow;
}


///////////////////////////////////////////
//  All rows have been read and stored in our dstbuffer.
//   give that buffer to OpenGL

glTexImage2D( /* too lazy to look up details of this function.  Give it the 'dstbuffer' buffer, with dims dstwidth,dstheight, and format GL_ARGB */ );

///////////////////////////////////////////
// Done!  Clean up our allocated buffers
delete[] srcrow;
delete[] dstbuffer;
Last edited on
I had an assignment a few semesters back where I needed to read in a bmp file. Maybe reding the source will help you out a bit http://home.comcast.net/~svogel76/Projects/Console_app/bitmap.cpp It just reads in a bmp file and saves it to a different file.
Topic archived. No new replies allowed.