PDA

View Full Version : Loading a image



Justin
12-21-2010, 06:51 AM
I've manages to create a image loading function that works. As references here it is with comments.



ImageBMP::ImageBMP(){
/*
Zero everything out
*/
mBitmap = NULL;
mWidth = 0;
mHeight = 0;
mStride = 0;
mType = 0;

GdiFlush();
}
ImageBMP::~ImageBMP(){
FreeBMP();
}
void ImageBMP::FreeBMP(){
if(mBitmap)
DeleteObject(mBitmap);

mBitmap = NULL;
mWidth = 0;
mHeight = 0;
mStride = 0;
mType = 0;
imgData = NULL;
}
unsigned char* ImageBMP::GetImageData(int which_line){
return (imgData + mStride * which_line);
}
bool ImageBMP::SizeImg(int w, int h, int channels){
/*
To begin with, we clear the memory of the image incase anything
was already loaded in. We then create a temporary device
context and initialize our width, height, and type variables.
We also initialize our stride variable, but read further down for
more information on that.
*/
FreeBMP();
HDC tmpDC = CreateCompatibleDC(NULL);
if(!tmpDC){
MessageBox(NULL, "CreateCompatibleDC() failed!", "Error SizeImg()", MB_OK);
return false;
}

mWidth = w;
mHeight = h;
mType = channels;

/*
In windows and most other OS's. Some things are far easier
to accomplish when the data is dword aligned. dword = 2 words
or 2 words = 4 bytes (Some asm knowledge there). Anyhow, this
helps speed up drawing to the screen, we we get the true stride,
then modify it until it is evenly divided by 4. IF you ever
wondered when the hell you would use the modulus symbol, here it
is!
*/
mStride = mWidth * mType;
while((mStride % 4) != 0)
mStride++;

BITMAPINFO bInfo = {0};

/*
We set up the bitmap data here for when we create the image itself
from the file data. biSize is the size of the header. WE just pass
in the BitmapInfoHeader struct for this. biWidth and biHeight should
not need explanation. biPlanes will always be 1 regardless of what type
of image you load. Even MSDN says it MUST be 1. Next we have biBitCount
which is just the number of channels we have multiplied by 8 (8 bits per byte).
If you wanted to be an [censored] to someone, you could always ask "Hey, can you
save that in 3 byte format?" (24bit)
Anyways, moving along... We come across biCompression. Since no compression
should have happened to the image we are loading, we just tell it no compression
occured to the image data. Finally, biClrUsed is just a indicies tracker for images
in the 8 bit range for color platelets. We don't need it though so set it to 0.
*/
bInfo.bmiHeader.biSize = sizeof BITMAPINFOHEADER;
bInfo.bmiHeader.biWidth = mWidth;
bInfo.bmiHeader.biHeight = mHeight;
bInfo.bmiHeader.biPlanes = 1; // This will always be 1
bInfo.bmiHeader.biBitCount = mType * 8; // 3 channels = 24, 4 = 32
bInfo.bmiHeader.biCompression = BI_RGB; // No compression
bInfo.bmiHeader.biClrUsed = 0; // Always 0 when working with 24/32 bit bmps


/*
Finally, we actually create the image to store our data into. After creating it, we
check to see if it finished properly, then we delete the temporary device
context created earlier. Do a final check to make sure all is okay, then return true!
*/
mBitmap = CreateDIBSection(tmpDC, &bInfo, DIB_RGB_COLORS, (void**)&imgData, 0, 0);

DeleteDC(tmpDC);
if(!mBitmap){
MessageBox(NULL, "CreateDIBSection() failed!", "Error SizeImg()", MB_OK);
return false;
}
return true;
}
bool ImageBMP::LoadBMP(const char *filename){
/*
To start off, we erase any memory or garbage memory that might be in the
bitmap class already. After that we proceed to check and ensure a filename was
ACTUALLY passed into the function, if it was, we open the file and proceed to
read the data into our holder and then into OpenGL.
*/
FreeBMP();
if(!filename){
MessageBox(NULL, "Bad file name!", "Error LoadBMP()", MB_OK);
return false;
}

FILE * img = NULL;
fopen_s(&img, filename, "rb"); // Notice we read the file in byte by byte. 'rb' means Read Byte mode.

if(!img){
MessageBox(NULL, "fopen() failed!", "Error LoadBMP()", MB_OK);
return false;
}

// Stores information on the bitmap header when we load it in.
BITMAPFILEHEADER bHeader;

if(!fread(&bHeader, sizeof bHeader, 1, img)){
MessageBox(NULL, "fread() [BITMAPFILEHEADER] failed!", "Error LoadBMP()", MB_OK);
return false;
}

/*
Here, we check to make sure this is ACTUALLY a bmp file. You can rename a .jpg
to .bmp, but the compression still exists inside it. If we tried to load it
while it was still compressed, bad things will happen. We check to avoid that
bad situation. Note that memcmp returns true if the memory does NOT match. Don't
ask me why.
*/
if(memcmp(&bHeader.bfType, "BM", 2)){
MessageBox(NULL, "memcmp() detected false file type!", "Error LoadBMP()", MB_OK);
fclose(img); // Close the file since it is the wrong filetype.
return false;
}

// This contains information on the bitmap itself
BITMAPINFOHEADER bInfo;

// Read in the info for the bitmap.
if(!fread(&bInfo, sizeof(BITMAPINFOHEADER), 1, img))
{
MessageBox(NULL, "fread() [BITMAPINFOHEADER] failed!", "Error LoadBMP()", MB_OK);
fclose(img);
return false;
}

if((bInfo.biBitCount != 24) && (bInfo.biBitCount != 32)){
/*
Here, we checked if the image was 24 or 32 bits. If it is neither, then we just return false after
closing the file. This loader is currently unable to load monochrome, or 8 bit bmp images. We
will simply stick to 24 or 32.
*/
MessageBox(NULL, "Not a valid 24 | 32 bit image type!", "Error LoadBMP()", MB_OK);
fclose(img);
return false;
}

/*
After getting all the information from the header, we then set the image data up
and prepare it for loading in the actual image data itself. That is done
with the function call we created earlier in the class. We pass in the height
and the width. To send in the number of channels, it is the bit count / 8 (8 bits per byte),
and 3 bytes is the number of channels. Or 4...
*/
if(!SizeImg(bInfo.biWidth, bInfo.biHeight, bInfo.biBitCount/8)){
MessageBox(NULL, "Size() failed!", "Error LoadBMP()", MB_OK);
fclose(img);
return false;
}

/*
What is about to happen might look confusing but it is very straight forward!
First, we need to find out how many bits are on each line in the image.
Second, we need to find out how many of those bytes are padded since we want to avoid reading those in.
Finally, we read the data into the image handler.
*/
unsigned int bytesPerLine = mWidth * mType; // Get the number of bytes per line
unsigned int padCount = mStride - bytesPerLine; // Get the amount of padding involved.

// Loop through and read the data in
for(int i=0; i<mHeight; i++){
/*
i is the line number in the image we are on. In this case, i
will start at 0 being the top of the file. From each line, we need
to relocate our position
*/
unsigned char* pos = GetImageData(i); // Relocate our position

/*
Read in the image data
*/
if(!fread(pos, bytesPerLine, 1, img)){
MessageBox(NULL, "fread() at ImageData failed!", "Error LoadBMP()", MB_OK);
fclose(img);
return false;
}

/*
Skip over padding, remember that fseek returns 0 on succes, and negative values
for errors.
*/
if(fseek(img, 0, SEEK_CUR)){
MessageBox(NULL, "fread() at padding failed!", "Error LoadBMP()", MB_OK);
fclose(img);
return false;
}
}

// Loaded the image successfully, we now just do what we want with the image data
fclose(img);
return true;
}

That loads the image and this actually makes the texture.

bool LoadImage(char* file, GLuint tex[], GLuint id){
ImageBMP img;
if(!img.LoadBMP(file))
return false;

glGenTextures(1, &amp;tex[id]);
glBindTexture(GL_TEXTURE_2D, tex[id]);

glTexImage2D( GL_TEXTURE_2D,
0,
img.GetType(),
img.GetWidth(),
img.GetHeight(),
0,
GL_BGR_EXT,
GL_UNSIGNED_BYTE,
img.GetImageData(0));

gluBuild2DMipmaps( GL_TEXTURE_2D,
img.GetType(),
img.GetWidth(),
img.GetHeight(),
GL_BGR_EXT,
GL_UNSIGNED_BYTE,
img.GetImageData(0));

glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTE R,GL_LINEAR_MIPMAP_NEAREST);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTE R,GL_LINEAR);

/*
The functions below cause the image to be stretched properly to cover the border lines preventing
black lines from showing. Try commenting these out to see the effect.
*/
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);


return true;
}

For some reason though, I get a strange error. I am unable to draw any image that is loaded before the last image loaded in. So I can load in 3 images, but no matter which one I bind, only the last one will be drawn. Here is my draw function.


glEnable(GL_TEXTURE_2D);
glPushMatrix();
glTranslatef(m_position.x(), m_position.y(), m_position.z());
glRotatef(90, 1, 0, 0);
glRotatef(m_facingAngle, 0, 0, 1);
glColor3f(1, 1, 1);
glBegin(GL_QUADS);
glBindTexture(GL_TEXTURE_2D, textures[0]);
glTexCoord2i(0, 1); glVertex3f(-.8, -.5, 0);
glTexCoord2i(1, 1); glVertex3f(.8, -.5, 0);
glTexCoord2i(1, 0); glVertex3f(.8, .5, 0);
glTexCoord2i(0, 0); glVertex3f(-.8, .5, 0);
glEnd();
glPopMatrix();
glDisable(GL_TEXTURE_2D);

Does anyone have any idea on why this happens? Do you need more info?

randall
12-21-2010, 08:08 AM
You are always binding same texture:
glBindTexture(GL_TEXTURE_2D, textures[0]);

Do not call glBindTexture in glBegin/glEnd block.

GL_INVALID_OPERATION is generated if glBindTexture is executed between the execution of glBegin and the corresponding execution of glEnd.

Justin
12-21-2010, 12:36 PM
Thanks, the error was I was calling it after glBegin. Thanks for the follow up.