Storing multiple images to be loaded later

Started by
7 comments, last by MichaelBarth 11 years, 10 months ago
What I'm currently working on is a blend of OpenGL, SDL, and general programming, so I figured this would be the best place to post.

Alright, I have a skeleton program here, I created a screen with SDL using OpenGL that displays a blank screen and does nothing so far. I already have the ability to load images and convert them into usable OpenGL textures. Here is that function:


GLuint loadImage(const char* file)
{
GLuint texture; // This is a handle to our texture object
SDL_Surface *surface; // This surface will tell us the details of the image
GLenum texture_format;
GLint nOfColors;
if ((surface = IMG_Load(file))) {
surface = SDL_DisplayFormatAlpha(surface);
if ((surface->w & (surface->w - 1)) != 0)
{
FILE* log = fopen("Castlevania.log", "a+");
fprintf(log, "WARNING: %s's width is not a power of 2.\n", file);
fclose(log);
if (DefaultDebug)
printf("WARNING: %s's width is not a power of 2.\n", file);
}
// get the number of channels in the SDL surface
nOfColors = surface->format->BytesPerPixel;
if (nOfColors == 4) // contains an alpha channel
{
if (surface->format->Rmask == 0x000000ff)
texture_format = GL_RGBA;
else
texture_format = GL_BGRA;
} else if (nOfColors == 3) // no alpha channel
{
if (surface->format->Rmask == 0x000000ff)
texture_format = GL_RGB;
else
texture_format = GL_BGR;
} else {
FILE* log = fopen("Castlevania.log", "a+");
fprintf(log, "FATAL ERROR: %s is not truecolor.\n", file);
fclose(log);
if (DefaultDebug)
printf("FATAL ERROR: %s is not truecolor.\n", file);
if (surface)
SDL_FreeSurface(surface);
return NULL;
}
// Have OpenGL generate a texture object handle for us
glGenTextures( 1, &texture );
// Bind the texture object
glBindTexture( GL_TEXTURE_2D, texture );
// Set the texture's stretching properties
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
// Edit the texture object's image data using the information SDL_Surface gives us
glTexImage2D( GL_TEXTURE_2D, 0, nOfColors, surface->w, surface->h, 0,
texture_format, GL_UNSIGNED_BYTE, surface->pixels );
}else {
FILE* log = fopen("Castlevania.log", "a+");
fprintf(log, "FATAL ERROR: SDL could not load %s.\n", file);
fclose(log);
if (DefaultDebug)
printf("FATAL ERROR: SDL could not load %s.\n", file);
if (surface)
SDL_FreeSurface(surface);
return NULL;
}
// Free the SDL_Surface only if it was successfully created
if (surface)
SDL_FreeSurface(surface);

return texture;
}


What I want to do is store a bunch of images in something like a .dat, .bin, or .whatever file so that I can load them. I assume I'd have to read them as files and use fwrite to write them to some sort of binary file. Okay, that's no problem, but how would I load them? Or would I be able to write the GLuint textures directly somehow to make them easy to load? Any help is appreciated.
Advertisement
Why do you want to save all images in one file?

If the images are of the same size, a common technique is to use an "atlas". It could then be loaded as one single texture. Of course, the texture coordinates have to be adjusted accordingly. If the images are not of the same size, it is still possible, but a little more work needed. It is a technique sometimes used for loading fonts (also variable width).
[size=2]Current project: Ephenation.
[size=2]Sharing OpenGL experiences: http://ephenationopengl.blogspot.com/
Why wouldn't I? Most games have data stored in stuff like a .dat file and many other assorted file types. I'd want something like that, such as textures.dat or sounds.dat. I'm familiar with the atlas technique, even though I actually didn't know that was the name of it. I guess in order to do what I want to here I would need to create some kind of mini file system so I could store data, but I'm not quite sure how.
There is an obvious reason to not use a single file, and that is that it makes it harder to read and decode. Storing one image in every file is easy.

But i suppose you have other requirements, not shown above. Maybe you want to make it harder for other to hack and modify your images? This is, however, a more difficult requirement.

One solution could be to use a zip format? That way, you can use a standard zip program to create your files. See http://www.zlib.net/.
[size=2]Current project: Ephenation.
[size=2]Sharing OpenGL experiences: http://ephenationopengl.blogspot.com/
Spirrwell,

what you are going to want to do is save all of the file information (width, height, pixel format, etc.) that is needed for passing it along to various subsystems for loading that data type, such as


glTexImage2D( GL_TEXTURE_2D, 0, nOfColors, surface->w, surface->h, 0,
texture_format, GL_UNSIGNED_BYTE, surface->pixels );


the way i have done this in the past is simply by writing a struct that contained this information right before the pixel or sound data itself. so the file would look something like


[number of items : 5]

[item1 image info]
[item1 image data]

[item2 image info]
[item2 image data]

[item3 image info]
[item3 image data]

[item4 image info]
[item4 image data]

[item5 image info]
[item5 image data]

[end of file]



obviously this is not the optimal solution, nor would it be beneficial to a complex development environment. but i have a feeling you dont need some kind of crazy impressive over thought out solution. pack the files like this, and run compression afterwords if you want. then you can figure out how to improve on it if you so desire. theres nothing wrong with doing this by the way. some people are way too by the book.

and as a side note. if you store textures of the same size (file size not JUST dimensions) in their own pak files, you can improve performance by keeping track of a textures index in this file format and seek right to it in memory by using something like (image index * textureSize)
Alternatively you could store them in resource dll's.

Spirrwell,

what you are going to want to do is save all of the file information (width, height, pixel format, etc.) that is needed for passing it along to various subsystems for loading that data type, such as


glTexImage2D( GL_TEXTURE_2D, 0, nOfColors, surface->w, surface->h, 0,
texture_format, GL_UNSIGNED_BYTE, surface->pixels );


the way i have done this in the past is simply by writing a struct that contained this information right before the pixel or sound data itself. so the file would look something like


[number of items : 5]

[item1 image info]
[item1 image data]

[item2 image info]
[item2 image data]

[item3 image info]
[item3 image data]

[item4 image info]
[item4 image data]

[item5 image info]
[item5 image data]

[end of file]



obviously this is not the optimal solution, nor would it be beneficial to a complex development environment. but i have a feeling you dont need some kind of crazy impressive over thought out solution. pack the files like this, and run compression afterwords if you want. then you can figure out how to improve on it if you so desire. theres nothing wrong with doing this by the way. some people are way too by the book.


Heheh, I'm definitely not a by the book person. Hell, I don't even own a programming book, I'm just absorbing as much material online as I can. Anyway, I've got most of what I need, but I have a certain problem that I was expecting, writing and reading the pixel data. I have the hardest time figuring this out. Basically there's a void pointer to pixels:


void* pixels


This is the one area where I really hate pointers. How do I fwrite this pointer? I tried to research it and I ended up with something that doesn't make sense:


int y;
for (y = 0; y < info->Height; y++)
{
fwrite(info->pixels+y*(info->Width * info->Height * info->NumberOfColors), 1, info->Width * info->NumberOfColors, iInf);
}


I also have my struct and a GetImageInfo function as such:


typedef struct ImageInfo
{
int NumberOfColors, Width, Height;
GLenum textureFormat;
void* pixels;
} ImageInfo;
ImageInfo* GetImageInfo(int numofcolors, int width, int height, GLenum texFormat, void* pixels)
{
ImageInfo* info = (ImageInfo*)malloc(sizeof(ImageInfo));
info->NumberOfColors = numofcolors;
info->Width = width;
info->Height = height;
info->textureFormat = texFormat;
info->pixels = pixels;
return info;
}

This is the one area where I really hate pointers. How do I fwrite this pointer? I tried to research it and I ended up with something that doesn't make sense:


ahh yes, It's been a while since i've used SDL but i would assume that the void pointer to your pixels is the actual image data.

its void because of varrying pixel formats, 8 bit 16 bit , whatever.

i would reccomend casting it to a data type of that size. for example if 8bit, you could just use an unsigned char to represent each pixel.

if its 16 bits per pixel, you could do

typedef struct MyPixel
{
unsigned short r;
unsigned short g;
unsigned short b;
//unsigned short a; //alpha component if needed;
}


for 32 bits per pixel, use an unsigned int. and i'm unsigned because i'm assuming these will never be negative. it probably doesnt matter if signed or unsigned.

this way you can use a for loop, and indices to iterate through your pixel data. just cast to your new type


MyPixel* pixelData= (pixelData *) (memory address of your pixel data loaded from file);


and you will know the length of this array by the width/height values in the image info struct

i hope that helps sorry im kinda in a rush right now ph34r.png

i hope that helps sorry im kinda in a rush right now ph34r.png


Ah, it's fine. Even though I couldn't really make too much of that, it did give me an idea. There's the getpixel and putpixel functions that are for some reason not built into SDL.

I was able to write my image just fine and ended up with a 9 MB file. Not good in terms of space, but I'll worry about that later and figure out how to compress it. The problem I'm having now is to read the pixels and write them back into an SDL Surface. (I'd actually just like to write them into a void* pixels to make it that much simpler, but I don't know how to do that)

Here's how I'm reading it:


Uint32 rmask, gmask, bmask, amask;
/* SDL interprets each pixel as a 32-bit number, so our masks must depend
on the endianness (byte order) of the machine */
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
rmask = 0xff000000;
gmask = 0x00ff0000;
bmask = 0x0000ff00;
amask = 0x000000ff;
#else
rmask = 0x000000ff;
gmask = 0x0000ff00;
bmask = 0x00ff0000;
amask = 0xff000000;
#endif
info->surface = SDL_CreateRGBSurface(SDL_SWSURFACE, info->Width, info->Height, screenBpp, rmask, gmask, bmask, amask);
for (int y = 0; y < info->Width; y++)
{
for (int x = 0; x < info->Width; x++)
{
Uint32 pixel;
fread(&pixel, sizeof(Uint32), 1, iInf);
putpixel(info->surface, x, y, pixel);
}
}


And here's how it's written:


for (int y = 0; y < info->Height; y++)
{
for(int x = 0; x < info->Width; x++)
{
Uint32 pixel = getpixel(info->surface, x, y);
fwrite(&pixel, sizeof(Uint32), 1, iInf);
}
}


Any help is appreciated Sorry this became more of an API->SDL topic, if I'd known I would've posted it in the API section.

Edit:
D'oh. I'm a moron, I forgot to post the getpixel and putpixel functions:


Uint32 getpixel(SDL_Surface *surface, int x, int y)
{
int bpp = surface->format->BytesPerPixel;
/* Here p is the address to the pixel we want to retrieve */
Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * bpp;
switch(bpp) {
case 1:
return *p;
case 2:
return *(Uint16 *)p;
case 3:
if(SDL_BYTEORDER == SDL_BIG_ENDIAN)
return p[0] << 16 | p[1] << 8 | p[2];
else
return p[0] | p[1] << 8 | p[2] << 16;
case 4:
return *(Uint32 *)p;
default:
return 0; /* shouldn't happen, but avoids warnings */
}
}
void putpixel(SDL_Surface *surface, int x, int y, Uint32 pixel)
{
int bpp = surface->format->BytesPerPixel;
/* Here p is the address to the pixel we want to set */
Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * bpp;
switch(bpp) {
case 1:
*p = pixel;
break;
case 2:
*(Uint16 *)p = pixel;
break;
case 3:
if(SDL_BYTEORDER == SDL_BIG_ENDIAN) {
p[0] = (pixel >> 16) & 0xff;
p[1] = (pixel >> 8) & 0xff;
p[2] = pixel & 0xff;
} else {
p[0] = pixel & 0xff;
p[1] = (pixel >> 8) & 0xff;
p[2] = (pixel >> 16) & 0xff;
}
break;
case 4:
*(Uint32 *)p = pixel;
break;
}
}

This topic is closed to new replies.

Advertisement