ICO文件格式的演化

    技术2022-06-09  67

    原文: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必须为0idType必须为1idCount表示这个图标里有多少个图像。ICO文件其实是由一组图像组成的。理论上来说,每个图像应该是同样的内容,但是不同的大小和颜色深度。当然,如果你要让一个16x16的图像和32x32的图像看起来完全不像,那也是可以的,不过用户可能会觉得很怪。

    idCount之后是一个ICONDIRECTORY结构的数组。当然,数组的长度由idCount指定。

    struct IconDirectoryEntry {

        BYTE  bWidth;

        BYTE  bHeight;

        BYTE  bColorCount;

        BYTE  bReserved;

        WORD  wPlanes;

        WORD  wBitCount;

        DWORD dwBytesInRes;

        DWORD dwImageOffset;

    };

    bWidthbHeight表示了图像的大小。一开始呢,只支持1255的大小。从Windows95NT 4.0)开始,用0来表示大小是256

    wBitCountwPlanes用来描述图像的颜色深度。对单色图标来说,它们都为1bReserved必须为0dwBytesInResdwImageOffset用来描述实际图像数据的位置(相对于ICO文件的开始)和数据字节数。

    然后,还有个悲剧的bColorCount。它被假定的认为等于图像的颜色数量,也就是说:

    bColorCount = 1 << (wBitCount * wPlanes)

    如果 wBitCount * wPlanes 大于等于8,则bColorCount0

    在现实情况中,很多人懒得填写bColorCount的值,即使是4色或16色的图标,也把它设为0。从Windows XP开始,Windows会检测这个常见的错误,但是对于planar位图来说,这个自动纠错的机制还是有些问题的。幸运的是,几乎没人使用planar位图了。但是你还是不应该依赖于Windows提供的自动纠错机制,而正确的填写bColorCount的值。错误的bColorCount意味着,由于提供了错误的颜色深度信息,Windows可能会在ico文件中选择一个不怎么好的图像。

    我将假定单色图标出现在彩色图标之前,当然这不是事实,只是让故事更好说一些。

    单色图标由两个位图组成,分别被称为AND(或mask)和XOR(或image,或者当说到彩色图标的时候,color)。绘制图标分为两个步骤:首先maskscreen操作,然后和image异或操作。也就是说,

    pixel = (screen AND mask) XOR image

    maskimage选择适当的值,可以覆盖所有单色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中相应的像素拷贝到屏幕上。

    maskimage位图在物理上是保存为单个但是双倍高度的DIB。首先是image位图,然后是mask。(由于DIB自下而上的保存格式,所以如果你实际的观察一个位图,mask在上面而image在下面)。(译注:bmp文件格式使用数学坐标系,也就是说(0,0)在左下角。bmp文件中的数据最开始的数据是最下面一行像素。)

    按照格式,每个图标的图像保存为BITMAPINFO结构的形式(根据其中BITMAPINFOHEADER来决定有没有调色板数据),然后是image的像素数据和mask的像素数据。其中biCompression(译注:BITMAPINFOHEADER中的字段,后面的biWidthbiHeight都是)必须为BI_RGB。由于是双倍高度的位图,所以biWidth为图像的宽度,biHeight为图像高度的两倍。例如:16x16的图标,则宽度为16,高度为16 * 2 = 32

    单色图标就这么多了。下次说彩色图标。

    那么,对于下面的故事,你应该可以了解原因了。

    一位顾客联系了msshell team,说他们随便怎么努力,windows就是不在他们的ico文件中选择一个他们想要的图像。Windows不知怎么,就是使用一个颜色数比较少的图标。例如:在32bpp的显示模式下,Windows总是选择16色(4bpp)的图像,即使有32bpp的图像。

    仔细看一下这个ICO文件可以发现,IconDirectoryEntry中的bColorCount都被设置成了1,无论实际图像的颜色深度是什么。这个ICO文件的目录有一些单色的图像。348x48的单色图像,332x32的图像,316x16的图像得到了这些信息,Windows认为在这些选择中,我应该使用一个单色的图标Windows从中随机的选择一个,然后解析到了位图的数据,发现哦,怎么回事,它实际上是个16色图标。好吧,我仍然能加载它

    总之,这个ICO文件没有被正确的创建。用一个16进制编辑器中修正每个IconDirectoryEntry结构,图标文件总算是如预期得工作了。这个顾客感谢了我们的努力,并说会把这个问题提交给他们的图形设计部门的。

     

     

    (二)彩色图标

     

    上次,我们说了单色图标。那么彩色图标是怎么样的呢?(一个ICO中同时包含单色和彩色的图标是合法的。为什么不支持2色的图标呢?毕竟ICO文件是支持16色或高彩色(译注:16位色)的图标的。)

    ICO中的彩色图像和单色图像的格式基本上的一样的。唯一的区别就是image位图是彩色的了。(mask依然是单色的。)(译注:无论image部分是多少bpp的像素,mask永远是1bpp。并且每行像素的数据必须是4字节对齐的。例如:48*48大小的16色图标,像素部分的数据大小是1536字节,你们可以算一下为什么是这样?)

    也就是说,图像格式包含一个BITMAPINFOHEADER,其中bmWidth是图像的宽度,bmHeight是图像宽度的两倍。接着是调色板、image像素的数据和mask像素的数据。

    要注意到的是,这并不是一个标准的位图格式。因为同时包含了imagemask,所以高度是两倍。但是,像素的格式在中间的时候改变了。

    还有个限制:只支持4bpp8bpp16bpp0RGB 32bpp,而不支持24bpp。对于彩色图标,biCompression支持BI_RGBBI_BITFIELDS(如果位图是16bpp32bpp)。

    绘制图标的机制和单色图标是一样的:首先maskscreen与,然后和image异或。也就是说:

    pixel = (screen AND mask) XOR image

    然而对于彩色像素来说,异或操作并不是有意义的操作。并不是通常所说的谁都知道,紫红异或浅绿等于黄色。或者8bpp上,蓝色异或淡黄褐色等于杏黄色(因为淡黄褐色在调色板的索引是56,蓝色的索引是1,而索引57的颜色是杏黄色),而在32bpp上等于#F0EA29”。唯一有意义的是和黑色作异或,你会得到对于任意颜色Q来说,黑色 XOR Q = Q”

    mask

    image

    结果

    操作

    0

    Q

    (screen AND 0) XOR Q = Q

    图标的像素

    1

    0

    (screen AND 1) XOR 0 = screen

    不变

    1

    Q

    (screen AND 1) XOR Q = screen XOR Q

    不确定

    要使像素透明,你要把mask设置成白色,并且image为黑色。而要使用来自于图标中的像素,则mask设置成黑色,image的颜色设置成你想要的随便什么颜色。

     

     

    (三)带透明通道的图标

     

    Windows XP引入了带8位透明通道的图标。而在这之前,只有一个称为mask1位透明通道。

    ICO文件中带透明通道的图像很简单。既然ICO格式支持0RGB 32bpp的图像,那么为了支持透明通道,只要改成ARGB 32bpp就可以了。当窗口管理器看到一个32bpp的图标,它会检查一遍透明通道。如果都是零,则认为是0RGB格式,否则就认为是ARGB格式。而文件的其它部分和以前一样。

    要注意文件的其它部分和以前一样。也就是,你仍然要提供mask。我看到有些人懒得提供一个有意义的mask,而把它都填零。这样,除了一些特殊情况会出现问题,一般都能正常工作。

    基本上有三种绘制带透明通道的图标。

    DrawIcon(DI_NORMAL):最一般的调用。对于有透明通道的情况下,则将image和目的图像alpha混合。

    DrawIcon(DI_IMAGE):将图标中image部分覆盖到目的上。

    DrawIcon(DI_MASK):只绘制图标中的mask副本,并覆盖的目的上。

    DI_IMAGEDI_MASK选项让应用程序能够绘制图标中两个图像中的其中一个。这样应用程序可以更好的控制图标的绘制流程。例如:他们可以通过mask给图标加一个阴影。mask表示图标的那部分是不透明的,需要产生阴影。

    至此,你应该了解到那些把mask都填零的人为什么在大多数情况下都是幸运的。因为大多数程序使用DI_NORMAL绘制图标。那个错误的mask从来不被使用,错误从没被发现。直到某个程序想对图标作一些特殊效果,并使用了DI_MASK(或者调用GetIconInfo,得到其中的hbmMask),然后那个错误mask才导致图标很难到。

    讽刺的是,那些错误地把mask填零的人总是反过来说用透明通道的图标,在情况XY下,结果很难看。ms的程序员都是sb。这更说明了Widows是一堆bug组成的。他们没有意识到,难看的结果是由他们的错误所造成的。

     

    (四)PNG格式

     

    为了支持PNG,很自然地将BITMAPINFOHEADER中的biCompression改为BI_PNG即可。这时,image将使用PNG格式而不是DIB。毕竟这就是为什么要有biCompression字段的原因:提供对将来系统的兼容。

    但是情况不是那么简单的。如果你在ICO文件中使用这种格式,那么很多有名的图标编辑软件在打开这种PNG压缩的图标文件都会崩溃。

    问题在于解析图标文件的代码中并没有考虑到这个新的BI_PNG选项,并把它认为解析失败(或者根本就没有碰到这种错误的发生)。解决方法就是彻底改变PNG压缩图标的格式,使得那些程序提早的发现文件格式的错误。(这有些违背了参透测试要求调整数据使得错误发生在更深入更危险的阶段了。)

    这种荒谬的为了增加兼容性的方式,反而降低的兼容性。

    PNG格式图标的image简单地由一个PNG图像组成。图像必须是32bpp ARGB格式。没有前面的BITMAPINFOHEADER,也没有单色的mask

    由于我们不得不违背了以前ICO文件格式的兼容性,这也解决了上次提及的人们提供了错误mask的问题。用了PNG格式的图像,你不再需要提供mask了。mask自然而然的由PNG的透明通道提供。提供得越少越不容易犯错。

     


    最新回复(0)