ICO文件格式的演化(一):单色图标
原文:http://blogs.msdn.com/b/oldnewthing/archive/2010/10/18/10077133.aspx
这个礼拜我将花时间来说一下ICO文件格式的演化。首先图标资源的格式和图标文件的格式是不同的,这个我改日再说。
ICO文件有一个固定的文件头:
typedef struct ICONDIR {
WORD idReserved;
WORD idType;
WORD idCount;
ICONDIRENTRY idEntries[];
} ICONHEADER;
idReserved必须为0,idType必须为1。idCount表示这个图标里有多少个图像。ICO文件其实是由一组图像组成的。理论上来说,每个图像应该是同样的内容,但是不同的大小和颜色深度。当然,如果你要让一个16x16的图像和32x32的图像看起来完全不像,那也是可以的,不过用户可能会觉得很怪。
idCount之后是一个ICONDIRECTORY结构的数组。当然,数组的长度由idCount指定。
struct IconDirectoryEntry { BYTE bWidth; BYTE bHeight; BYTE bColorCount; BYTE bReserved; WORD wPlanes; WORD wBitCount; DWORD dwBytesInRes; DWORD dwImageOffset; };
bWidth和bHeight表示了图像的大小。一开始呢,只支持1到255的大小。从Windows95(NT 4.0)开始,用0来表示大小是256。
wBitCount和wPlanes用来描述图像的颜色深度。对单色图标来说,它们都为1。bReserved必须为0。dwBytesInRes和dwImageOffset用来描述实际图像数据的位置(相对于ICO文件的开始)和数据字节数。
然后,还有个悲剧的bColorCount。它被假定的认为等于图像的颜色数量,也就是说:
bColorCount = 1 << (wBitCount * wPlanes)
如果 wBitCount * wPlanes 大于等于8,则bColorCount为0。
在现实情况中,很多人懒得填写bColorCount的值,即使是4色或16色的图标,也把它设为0。从Windows XP开始,Windows会检测这个常见的错误,但是对于planar位图来说,这个自动纠错的机制还是有些问题的。幸运的是,几乎没人使用planar位图了。但是你还是不应该依赖于Windows提供的自动纠错机制,而正确的填写bColorCount的值。错误的bColorCount意味着,由于提供了错误的颜色深度信息,Windows可能会在ico文件中选择一个不怎么好的图像。
我将假定单色图标出现在彩色图标之前,当然这不是事实,只是让故事更好说一些。
单色图标由两个位图组成,分别被称为AND(或mask)和XOR(或image,或者当说到彩色图标的时候,color)。绘制图标分为两个步骤:首先mask和screen作“与”操作,然后和image作“异或”操作。也就是说,
pixel = (screen AND mask) XOR image
为mask和image选择适当的值,可以覆盖所有单色blt的操作。
mask | image | 结果 | 操作 |
0 | 0 | (screen AND 0) XOR 0 = 0 | 黑色 |
0 | 1 | (screen AND 0) XOR 1 = 1 | 白色 |
1 | 0 | (screen AND 1) XOR 0 = screen | 不变 |
1 | 1 | (screen AND 1) XOR 1 = NOT screen | 反色 |
概念上来说,mask指定了image的像素是否拷贝到屏幕上。mask中黑色的像素表示要将image中相应的像素拷贝到屏幕上。
mask和image位图在物理上是保存为单个但是双倍高度的DIB。首先是image位图,然后是mask。(由于DIB自下而上的保存格式,所以如果你实际的观察一个位图,mask在上面而image在下面)。(译注:bmp文件格式使用数学坐标系,也就是说(0,0)在左下角。bmp文件中的数据最开始的数据是最下面一行像素。)
按照格式,每个图标的图像保存为BITMAPINFO结构的形式(根据其中BITMAPINFOHEADER来决定有没有调色板数据),然后是image的像素数据和mask的像素数据。其中biCompression(译注:BITMAPINFOHEADER中的字段,后面的biWidth和biHeight都是)必须为BI_RGB。由于是双倍高度的位图,所以biWidth为图像的宽度,biHeight为图像高度的两倍。例如:16x16的图标,则宽度为16,高度为16 * 2 = 32。
单色图标就这么多了。下次说彩色图标。
那么,对于下面的故事,你应该可以了解原因了。
一位顾客联系了ms的shell team,说他们随便怎么努力,windows就是不在他们的ico文件中选择一个他们想要的图像。Windows不知怎么,就是使用一个颜色数比较少的图标。例如:在32bpp的显示模式下,Windows总是选择16色(4bpp)的图像,即使有32bpp的图像。
仔细看一下这个ICO文件可以发现,IconDirectoryEntry中的bColorCount都被设置成了1,无论实际图像的颜色深度是什么。这个ICO文件的“目录”说“有一些单色的图像。3个48x48的单色图像,3个32x32的图像,3个16x16的图像” 得到了这些信息,Windows认为“在这些选择中,我应该使用一个单色的图标”。Windows从中随机的选择一个,然后解析到了位图的数据,发现“哦,怎么回事,它实际上是个16色图标。好吧,我仍然能加载它”
总之,这个ICO文件没有被正确的创建。用一个16进制编辑器中修正每个IconDirectoryEntry结构,图标文件总算是如预期得工作了。这个顾客感谢了我们的努力,并说会把这个问题提交给他们的图形设计部门的。