Blizzard's BLP image format

Recommended Posts

Magos    218
In Warcraft 3 Blizzard uses their own image format - blp which contains multiple JPEG images with a shared header, one per mipmap (the mipmaps are stored in the image). Now each image shares one single header so to construct a JPEG image you append the header with any of the mipmap blocks and you get a usable JPEG image. I've written a loader that does this and it works fine. The problem however is when I'm trying to write a saver for this image format. How do you create jpeg images so that all mipmaps use the same header? Blizzard managed somehow, but I'm not sure how. I naively tried to take the header of the first mipmap but when appended to the others it generates screwed up images all of the size as the first one. I'm not surprised, since they use the same header. Does anyone have any ideas? If the width/height information lies in the header, how can one single header be used for all mipmaps (which has different sizes)??? I'm kinda lost here!

Share on other sites
Cypher19    768
Can't you assume that successive mipmaps will have their height and width divided by 2?

Share on other sites
Code-R    136
they actually use JPEGs for textures??!!

Share on other sites
Ganoosh_    173
Well if your loader works, what did you do for size in the loader? Anywas there's probably some procedural interval. Like half the size as the previous or something. Or it's in the header but I have no idea but try it out, I'm sure you'll get it eventually.

Share on other sites
Magos    218
I guess my question is, after generating the first mipmap how can I generate the next mipmap so it uses the same JPEG header as the first? If I generate another image it won't have the same huffman tables etc... as the previous, right?

Share on other sites
Guest Anonymous Poster
Quote:
 Original post by Code-Rthey actually use JPEGs for textures??!!

You seem surprised, why?

Share on other sites
Code-R    136
for one, it's lossy, the bad way, not DXT way:
[url]http://www.sjbaker.org/steve/omniv/jpegs_are_evil_too.html[/url]

Share on other sites
ZQJ    496
Quake 3 used JPEGs for non-alpha blended texturing as well, I didn't see any really bad artifacts there.

Share on other sites
Code-R    136
wasn't Q3A using TGAs?

Share on other sites
ZQJ    496
*checks* Nope, JPEGs. There are TGAs in there too, but mostly JPEGs.

Share on other sites
Darjk    124
We use this function to load out BLP textures in a project I'm involved with.
This is used to load BLP textures from World of Warcraft, but I'm pretty sure that they didn't modify their BLP format from Warcraft3 so this should work with it aswell.

void TextureManager::LoadBLP(GLuint id, Texture *tex){	// load BLP texture	glBindTexture(GL_TEXTURE_2D, id);	int offsets[16],sizes[16],w,h;	GLint format;	char attr[4];	MPQFile f(tex->name.c_str());	if (f.isEof()) {		tex->id = 0;		return;	}	f.seek(8);	f.read(attr,4);	f.read(&w,4);	f.read(&h,4);	f.read(offsets,4*16);	f.read(sizes,4*16);	tex->w = w;	tex->h = h;	bool hasmipmaps = attr[4]>0;	int mipmax = hasmipmaps ? 16 : 1;	if (attr[0] == 2) {		// compressed		unsigned char *ucbuf;		if (!supportCompression) ucbuf = new unsigned char[w*h*4];			format = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;		int blocksize = 8;		// guesswork here :(		if (attr[1]==8) {			format = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;			blocksize = 16;		} else {			if (!attr[3]) format = GL_COMPRESSED_RGB_S3TC_DXT1_EXT;		}		tex->compressed = true;		unsigned char *buf = new unsigned char[sizes[0]];		// do every mipmap level		for (int i=0; i<mipmax; i++) {			if (w==0) w = 1;			if (h==0) h = 1;			if (offsets[i] && sizes[i]) {				f.seek(offsets[i]);				f.read(buf,sizes[i]);				int size = ((w+3)/4) * ((h+3)/4) * blocksize;				if (supportCompression) {					glCompressedTexImage2DARB(GL_TEXTURE_2D, i, format, w, h, 0, size, buf);				} else {					decompressDXTC(format, w, h, size, buf, ucbuf);					glTexImage2D(GL_TEXTURE_2D, i, GL_RGBA8, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, ucbuf);				}			} else break;			w >>= 1;			h >>= 1;		}		delete[] buf;		if (!supportCompression) delete[] ucbuf;	}	else if (attr[0]==1) {		// uncompressed		unsigned int pal[256];		f.read(pal,1024);		unsigned char *buf = new unsigned char[sizes[0]];		unsigned int *buf2 = new unsigned int[w*h];		unsigned int *p;		unsigned char *c, *a;		int alphabits = attr[1];		bool hasalpha = alphabits!=0;		tex->compressed = false;		for (int i=0; i<mipmax; i++) {			if (w==0) w = 1;			if (h==0) h = 1;			if (offsets[i] && sizes[i]) {				f.seek(offsets[i]);				f.read(buf,sizes[i]);				int cnt = 0;				p = buf2;				c = buf;				a = buf + w*h;				for (int y=0; y<h; y++) {					for (int x=0; x<w; x++) {						unsigned int k = pal[*c++];						k = ((k&0x00FF0000)>>16) | ((k&0x0000FF00)) | ((k& 0x000000FF)<<16);						int alpha;						if (hasalpha) {							if (alphabits == 8) {								alpha = (*a++);							} else if (alphabits == 1) {								//alpha = (*a & (128 >> cnt++)) ? 0xff : 0;								alpha = (*a & (1 << cnt++)) ? 0xff : 0;								if (cnt == 8) {									cnt = 0;									a++;								}							}						} else alpha = 0xff;						k |= alpha << 24;						*p++ = k;					}				}				glTexImage2D(GL_TEXTURE_2D, i, GL_RGBA8, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, buf2);			} else break;			w >>= 1;			h >>= 1;		}		delete[] buf2;		delete[] buf;	}	f.close();	if (hasmipmaps) {		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR);		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR_MIPMAP_LINEAR);	} else {		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);	}	// some models require texture wrapping though	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP_TO_EDGE);	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP_TO_EDGE);}

Naturally you would have to change things like "MPQFile f(tex->name.c_str());" and actually open the BLP file instead, but this should give you the gist of it.

Also the BLP format is closer to TGA's, not JPEG's.

Share on other sites
Magos    218
BLP can store both JPEG's and a palettizized version of the image. They started using palettes in War3 - FT and WoW mainly because the MPQ compression was good enough plus it didn't get lossy.

However, I already have functional loaders for both types. As mentioned in the first post it's the saver I'm interested in...

Share on other sites
Magos    218
Never mind this thread now, it's solved. It turned out that I was saving one mipmap less than neccessary (and Warcraft 3 wasn't smart enough to fix it or tell me :) ).

Share on other sites
Vexorian    152
By the way Magos, how do you load the first JPG mip map from a BLP? Cause I tried that based on the specifications but I get a JPG with wrong colors that is only openable by windows XP and not ijl

Share on other sites
Magos    218
I just appended the header with the first mipmap (of size Size[0] at offset Offset[0]) and got a working JPEG. Note that it has 4 channels (RGBA), not 3. If the colors are reversed it may be stored as BGR rather than RGB, can't remember right now. If this is the case just swap the R and B components after loading.

I put up my own interpretation of the BLP (and MDX) format here, it may (or may not) be of help:
http://Magos.TheJeffFiles.com/War3ModelEditor/

Share on other sites
Vexorian    152
Wow that seems to be one of the best tools ever made for war3 although the war3 community has been dying latelly. Thanks, it would of course be of help mostly because it is the first time in 3 months I finally find help about the subject