图标文件格式研究

  [原创文档,请勿转贴到其他网站]
常看到有人说,图标文件就是普通的位图文件。我不知道为什么这样说。其实图
标文件确实有点象位图文件,但它还是有自身的表达结构的。图标文件的开头就
是一个奇怪的格式,定义如下:
tagIconDir = record
   idReserved:WORD;
   idType:WORD;
   idCount:WORD;
   idEntries:array[0..0] of tagIconDirEntry;
end;
其中的 idReserved 是保留域,目前始终为 0 ,idType 不象位图文件那样定义为
文件类型,而是定义为资源类型,是图标的话,它是 $0001 ,是光标的话,它是
$0002 ,由此可见,在定义这个类型时,MS 完全是做为资源类文件而确定的,估
计当时留下开头一个保留域的原因,也是参考位图文件格式的定义方法而保留的,
只是不知道为什么后来一直没有给这个保留域正式确定名字。 idCount 表示的是
这个文件里包含了几个图标,最早的时候,它一直是个$0002 ,也就是我们常见
的一个16X16和一个32X32两张位图的图标,现在有些图标,比如在 XP 中,已经
高达 8 个位图了。接下来是一个 idEntries 的数组结构。这个结构的大小,不是始
终为 1 的一个数组,它需要根据图标数目 ( idCount ) 来确定真实的数组大小。

为了加深理解,让我们来看一个 Windows98 安装到 Windows 目录下的一个图标:
WinUpd.ico 的开头 22byte 的情况:
00 00 01 00 06 00 20 20 10 00 00 00 00 00 E8 02 00 00 66 00 00 00
这里红色部分就是保留域 idReserved,绿色部分是资源类型 idType,紫色的是指
出这个文件包含有的图标数目 idCount,这里可以看到是一个 $6 ,表示它包含有
6 个图标。后面紧接着开始的是第一个 idEntries 数组(因为有 6 个图标,所以总
共应该有 6 个这样的 idEntries 数组)。

下面就让我们看看 idEntries 数组是怎么定义的:
tagIconDirEntry = record
   bWidth:BYTE;// 图标图片的显示宽度
   bHeight:BYTE;// 图标图片的显示高度
   bColorCount:BYTE;// 图标图片的颜色数
   bReserved:BYTE;// 保留域总是 0
   wPlanes:WORD;// 图标图片的位面数
   wBitCount:WORD;// 图标图片的颜色深度
   dwBytesInRes:DWORD;// 图标图片占用的数据量
   dwImageOffset:DWORD; // 图标图片的开始位置
end;
这个结构是很固定的 16Byte 数据,其各自的含义上面已经标出来了。由于同一
个文件中的每一个图标都有一个这样的结构,所以它实际上指的是单个图标的具
体信息。

不知道为什么,Microsoft 从来没有正式文档对上面的结构定义做过声明,John _
Hornick 在 95 年为 VC 开发者写的唯一的一个描述,成了目前所有对图标感兴趣
的开发者的圣经。因为从我的观点看来,上面结构中的一些定义一直保持着它最
初设计者的最原始的思想,Borland 公司在自己的 Win32 开发环境中跳出 MS 的
约束,自己定义了一个可以和 Canvas 共存的图标类 —— TIconImage ,从而注
定了 Borland 公司将使用自己的方式解释图标。
在后面我们会看到,tagIconDirEntry 一直不能被 MS 的核心 API 吸收为正式成
员,除了其中的 dwBytesInRes 和 dwImageOffset 2 个成员以外, 其他成员基本
没有被使用,而这 2 个成员也是作为了 MS 文件读写 API 的用途。因此,正如MS
自己所说的那样,图标文件是 Shell 的成员,只在外壳存在的时候才有效。 (之一)



  [原创文档,请勿转贴到其他网站]
正如我们上面讲到的那样,图标是 Shell 的成员,Shell 在读文件时,是根据扩展
名来确定怎么解释这个文件的。当遇到 ico 文件时,它会读该文件的第二个 word
字节,以确定资源类型是否是 $0001 或 $0002 ,得到确定以后,进一步读取第
三个 word 字节,以便为 idEntries 分配足够的内存:
idEntries 的内存分配总量=tagIconDir.idCount * SizeOf(tagIconDirEntry)
由于 tagIconDirEntry 始终是 16Byte ,所以分配的内存数,只与 idCount 有关。
以此为基础,我们可以读到连续的多个 tagIconDirEntry 内容,从而确定每个图标
图片的开始位置( dwImageOffset )和包含的信息总量( dwBytesInRes )。

得到了图标图片的开始位置,就可以读取这个图片的内容了。图标图片,实际上
就是位图格式的图片。继续用上面例子中的 WinUpd.ico 图标为例,从第 7 个 Byte
开始,是第一个 idEntries 数组:20 20 10 00 00 00 00 00 E8 02 00 00 66 00 00 00
在这个数组中,我们可以找到 dwBytesInRes=$000002E8 和 dwImageOffset=
$00000066 。也就是说,从文件开头算起的第 $66 字节开始,到 $34E 的内容,
是第一个图标图片的全部内容。这个内容就是一个标准的位图格式,为了与正式
的位图格式有所区别,我们称它为 tagIconImage (注意别和 Borland 的混淆):
tagIconImage = record
   icHeader:TBitmapInfoHeader;
   icColors:array[0..0]of TRGBQuad;
   icXOR:array[0..0]of BYTE;
   icAND:array[0..0]of BYTE;
end;
从上面的结构我们会发现 icXOR 和 icAND 2 个成员。普通的位图信息里是没有这
2 个成员的。它们代表了什么?
没错,猜都可以猜到,这是 2 个位图像素信息。大家知道,图标在被显示时,是
利用遮罩方法将 2 副位图在同一个位置显示才产生任意轮廓的,先使用 XOR 位
图抠出需要显示的区域,然后再在抠出的区域中显示出需要显示的图形。由于这
个缘故,图标的位图格式中的位图信息头 ( TBitmapInfoHeader ) 是 2 个位图共用
的。它与普通位图头信息最大的不同是 TBitmapInfoHeader.biHeight 成员,显然
它是 2 副位图高度的总和。由于我不打算在这里细说位图格式,有关位图的知识
请参见《位图文件格式研究》。在下面的一篇里,我将利用上面介绍的知识,直
接按这个 ICON 的格式规范,利用一幅真彩位图,组装出一个自己的图标。 (之二)

 



  [原创文档,请勿转贴到其他网站]
基于对 Windows 绘图 API 的研究,我们可以得到一个基本的事实,那就是最底层
的绘图操作 DrawDIB中。DrawDibDraw 是 Windows 所有位图绘图最直接的调用
函数,它本身只需要位图信息头(TBitmapInfoHeader)。深入区分调色板模式和
真彩模式以后,我们可以认识到,Windows 只要从位图信息头中获取信息就足够
了,它借以解释在其后出现的数据应该如何处理。如果是调色板模式,其后的数
据,包含有调色板和像素点颜色索引,如果是真彩色,其后的数据直接就是像素
点的 RGB 颜色值。
知道了这个情况,我们可以简单地把上面提到的图标图形结构(tagIconImage)
理解为位图信息(tagBITMAPINFO)就对了。这 2 个结构,最初都是对调色板位
图进行的数据描述,到了真彩色年代时,MS 直接把调色板占用的位置也挪做像素
描述了。这样,一个基于真彩色的位图描述,就变得异常简单了,我们根本不需
要真的去画一幅图,而只需要对关键数据进行程序填充就可以让 Windows 工作
得很好。

下面的代码,直接按 Icon 格式的要求,把一个只要尺寸不大于 255 x 255 像素
的任意真彩位图,封装成标准图标格式的真彩图标(真实的位图宽高尺寸保持不
变,所以可以做出最大 255 x 255 的真彩图标来)。代码分成 2 个函数,把取得
的位图文件,首先送入函数 CheckBMP(const MS:TStream):Boolean 进行位图格式
检查,这里主要检查位图的宽、高尺寸和有无压缩,检查通过的话,顺便对图标
格式需要的基本数据进行填充。这个格式,我简化为 IconHand 数组。然后使用
函数 CoalitionICO(var MS:TMemoryStream):Boolean 进行正式的位图填充。

这个生成真彩色图标的函数不使用常见的绘图做法,它的代码如下:

function CoalitionICO(var MS:TMemoryStream): Boolean;
var
 M1,M2:TMemoryStream;
 Size:Longint;
 FValue:DWord;
begin
  M1:=TMemoryStream.Create;
  M2:=TMemoryStream.Create;
  Size:=MS.Size-14;
  MS.Position:=14;
try
  M1.SetSize(Size);
  MS.Read(M1.Memory^,Size);

  M2.SetSize(Size-40);
  FillChar(M2.Memory^,Size-40,0);
  M2.Position:=0;

  FValue:=IconHand[7]*2;
  M1.Seek(8,soFromBeginning);
  M1.Write(FValue,4);
  FValue:=M2.Size*2;
  M1.Seek(20,soFromBeginning);
  M1.Write(FValue,4);
  M1.Position:=0;

  MS.SetSize(0);
  MS.Write(IconHand,22);
  MS.Write(M1.Memory^,M1.Size);
  MS.Write(M2.Memory^,M2.Size);

  Result:=True;
finally
  FreeAndNil(M1);
  FreeAndNil(M2);
end;
end;

完整的源代码,请参见附件,里面同时附有编译完成的 exe 文件和几个演
示用的图片。本程序产生的真彩图标,对任何 Win32 开发工具都兼容。

=================================================
补充一个另外的做法是:
uses CommCtrl;

procedure BitmapToIcon(Bitmap:TBitmap;IconWidth,IconHeight:Integer;IconFileName:string='');
// 使用时,最好先把位图处理到合适的尺寸后进行转换
var
  ImgListHandle,
  IconHandle:THandle;
  n: Integer;
  Icon:TIcon;
begin
  if (Bitmap.Width<>IconWidth) or (Bitmap.Height<>IconHeight) then
   begin
    // 这里添加一些缩放原始图形到合适尺寸的代码...(未完成)
   end;
   
  ImgListHandle:= ImageList_Create(IconWidth,IconHeight,ILC_COLOR32,1,1);
  try
    n := ImageList_Add(ImgListHandle,Bitmap.Handle,0);
    IconHandle:= ImageList_GetIcon(ImgListHandle,n,ILD_NORMAL);
    if (IconHandle<>0) and (IconFileName<>'') then
     begin
      // 这里输出为标准的图标文件保存
      if lowercase(Copy(IconFileName,Length(IconFileName)-3,4))<>'.ico' then
       IconFileName:=IconFileName+'.ico';
      Icon:=TIcon.Create;
      Icon.Handle:=IconHandle;
      Icon.SaveToFile(IconFileName);
      Icon.Free;
     end;
  finally
    ImageList_Destroy(ImgListHandle);
  end;
end;
使用这个函数导出图标,一定要为Delphi打上我下文提供的图标修正补丁。
        (之三)


附件 (203 K)


  下面提供一个补丁程序,专门用来修正 Delphi 有关图标显示的问题。目前只支持 D5 和 D7 。

这个程序考虑了对Delphi的原始安装的保护,您可以今天为自己提供256色支持、
明天又改成对64K色的支持,但当任何时候,您打算恢复到原来的安装,只要按
下“恢复原始”按钮即可。如果您并没有为自己提供任何补丁,“恢复原始”是灰色
不可用的,如果您并不确定您当前在使用什么色的支持补丁,您只要运行这个补
丁程序,按下希望得到支持的颜色按钮,新的支持立刻生效。

在这个附件中,同时提供了一个演示程序,用来观察补丁应用前后的效果,请参
考内部 Readme.txt 文件的使用说明。

<补丁程序的界面画面帖在 2 楼帖子里了,一个帖子里只能上传一个文件,否则就要被冲掉,本文附件尺寸 136 KB>
============ 续完============


附件 (139 K)



  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值