• Create Account

## Blizzard's BLP image format

Old topic!

Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

15 replies to this topic

### #1Magos  Members

Posted 06 October 2005 - 09:11 AM

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!

### #2Cypher19  Members

Posted 06 October 2005 - 09:18 AM

Can't you assume that successive mipmaps will have their height and width divided by 2?

### #3Code-R  Members

Posted 07 October 2005 - 11:41 AM

they actually use JPEGs for textures??!!

### #4Ganoosh_  Members

Posted 07 October 2005 - 02:57 PM

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.

### #5Magos  Members

Posted 08 October 2005 - 04:03 AM

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?

### #6Anonymous Poster_Anonymous Poster_*  Guests

Posted 08 October 2005 - 04:06 AM

Quote:
 Original post by Code-Rthey actually use JPEGs for textures??!!

You seem surprised, why?

### #7Code-R  Members

Posted 08 October 2005 - 10:44 AM

for one, it's lossy, the bad way, not DXT way:
http://www.sjbaker.org/steve/omniv/jpegs_are_evil_too.html

### #8ZQJ  Members

Posted 09 October 2005 - 09:12 AM

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

### #9Code-R  Members

Posted 09 October 2005 - 10:52 AM

wasn't Q3A using TGAs?

### #10ZQJ  Members

Posted 09 October 2005 - 01:16 PM

*checks* Nope, JPEGs. There are TGAs in there too, but mostly JPEGs.

### #11Darjk  Members

Posted 09 October 2005 - 06:36 PM

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.


{
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);

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]);

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];

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]);

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.

### #12Magos  Members

Posted 10 October 2005 - 12:55 AM

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...

### #13Magos  Members

Posted 10 October 2005 - 06:12 AM

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 :) ).

### #14Vexorian  Members

Posted 11 October 2005 - 10:38 AM

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

### #15Magos  Members

Posted 12 October 2005 - 01:04 AM

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/

### #16Vexorian  Members

Posted 12 October 2005 - 01:40 AM

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

Old topic!

Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.