本文分享&备忘最近了解到的icon资源在windows平台下相关的一部分知识,所有测试代码都尽可能的依赖win32 API实现。更全面的知识,参考文末列出的”参考资料“。
关键字:Icon资源存储结构、windows shell下显示哪个图标、如何获取EXE各种长宽的显示图标。
一个icon资源(可以是*.ico文件,也可以是windows资源节区里的icon group),可以包含多张图片,这些图片有着各自的size或者颜色深度,这些图片可以是bmp格式或者png格式(vista之后支持,一般256*256时使用)。
一、Icon文件的组成
Icon文件结构由两部分组成:icon文件头和多张图片数据,图片可以是bmp、png:
1、Icon文件头
typedef struct
{
WORD idReserved; // Reserved (must be 0)
WORD idType; // Resource Type (1 for icons)
WORD idCount; // How many images?
ICONDIRENTRY idEntries[1]; // An entry for each image (idCount of 'em)
} ICONDIR, *LPICONDIR;
其中idReserved必须是0;idType对于ICON文件来说必须是1;idCount指明icon文件有多少张图片,也就指明了接下来有多少个ICONDIRENTRY结构体数据。
typedef struct
{
BYTE bWidth; // Width, in pixels, of the image
BYTE bHeight; // Height, in pixels, of the image
BYTE bColorCount; // Number of colors in image (0 if >=8bpp)
BYTE bReserved; // Reserved ( must be 0)
WORD wPlanes; // Color Planes
WORD wBitCount; // Bits per pixel
DWORD dwBytesInRes; // How many bytes in this resource?
DWORD dwImageOffset; // Where in the file is this image?
} ICONDIRENTRY, *LPICONDIRENTRY;
其中dwBytesInRes指明本图片占用多少字节,dwImageOffset指明本图片数据开始位置相对文件头的偏移。颜色深度:bColorCount = 1 << (wBitCount * wPlanes),如果大于等于8,则填0。
2、图片数据
如果是位图格式图片:
typdef struct
{
BITMAPINFOHEADER icHeader; // DIB header
RGBQUAD icColors[1]; // Color table
BYTE icXOR[1]; // DIB bits for XOR mask
BYTE icAND[1]; // DIB bits for AND mask
} ICONIMAGE, *LPICONIMAGE;
icHeader中只有部分信息是icon文件需要的。icXOR也称为“image“,icAND也称为“mask”。
一个有两张位图图片的ico文件,文件头内容(前0x15字节是icon文件头 + ICONDIRENTRY 的内容):
如果是PNG格式图片,则Icon文件头后边接着的是png数据格式,譬如:
icon中含有一张png图片,文件头内容(前0x15字节是icon文件头 + ICONDIRENTRY 的内容):
png格式API相关:虽然LoadImage在vista版本下可以加载含有png格式图片的ico文件,但是windows没有直接API支持应用程序对png、jpeg格式文件的解压使用。在显示png、jpeg格式数据时,有StretchDIBits、SetDIBitsToDevice两API可以使用,但必须先检查硬件是否支持,因为这属于硬件级别的解码支持。想在GetDIBits中使用BI_PNG,获取PNG格式的数据是行不通的,BITMAPINFOHEADER结构体中的biCompression值BI_PNG,仅仅在上述两API中生效。
为什么在icon文件中有png格式存储:*.ico文件中含有png格式,这是vista以及之后的版本才做的支持,目的是为了减小256*256 ico 文件的size,一般的ico编辑工具都会默认把ico文件中256*256 size的图片保存为png格式,这是微软推荐的保存格式(参考资料5)。xp以及之前的系统用不到256*256格式的ico,所以没有支持。
二、Icon在windows中的显示
这里的icon特指两种情形:(1)EXE中的icon group资源;(2)*.ico文件。
EXE文件在windows shell下显示哪个icon,按照MSDN文文件档(参考资料1)的介绍,是这样子:
桌面图标显示哪个ICON图片,有两个步骤,1、选择序号最小的RT_GROUP_ICON,优先字母序号,再找数字序号;2、在RT_GROUP_ICON里面选择合适的RC_ICON:(1)size跟要求的最接近;(2)如果size符合要求的有多个,则选择颜色深度跟显示系统颜色深度匹配的;(3)如果颜色深度没有匹配的,则选择在显示系统支持范围内颜色深度值大的;(4)如果所有的颜色深度都大于显示系统支持范围,则选择颜色深度最低的;(5)系统把8位以及大于8位的颜色深度当作相同深度,这时,系统选择最先遍历到的RC_ICON;(6)如果显示系统的颜色深度是8位,则系统会在16色(颜色深度4位) 和256色(颜色深度8位)之间选择16色的,并且使用系统默认调色板。(这一条规律似乎跟(3)矛盾,有人说这是在安全模式下才成立的)。
在RT_GROUP_ICON选定的情况下,按照我自己的实践测试:
自己绘制一个*.ico文件并对各个size做标记,可以发现windows7下exe在选择显示哪个图标的时候,只用到:16 * 16(文件夹内小图标),32 * 32(属性、桌面小图标), 48 * 48(中等图标), 256 * 256(大图标)这四种size,xp下则使用前3种。如果ico中含有png格式,则需要使用VS2008以及以上的IDE,否则rc无法对其进行编译。对于windows显示何种图标参考资料4、5、6有更详细的介绍。
三、从PE文件中提取icon资源
对于EXE文件,最简单的是使用SHGetFileInfo API,但只能获取16* 16,32 * 32 size的图标。该api如果只指定SHGFI_ICON,则表明使用默认的32*32,如果指定SHGFI_LARGEICON,则表明是32*32,如果指定SHGFI_SMALLICON,则表明是16*16。SHGetFileInfo 还可以根据文件后缀名获取某类文件的图标。
示例代码:
1、某文件的32 * 32 pixels icon
//获取exe的32 * 32 icon
SHFILEINFO sfi;
ZeroMemory(&sfi, sizeof(SHFILEINFO));
::SHGetFileInfo(L"D:\\a.exe", FILE_ATTRIBUTE_NORMAL, &sfi, sizeof(SHFILEINFO), SHGFI_ICON | SHGFI_LARGEICON);
HICON icon_handle = sfi.hIcon;
2、某类文件的32 * 32 pixels icon
//获取*.rmvb文件类型的32 * 32 icon
SHFILEINFO sfi;
ZeroMemory(&sfi, sizeof(SHFILEINFO));
::SHGetFileInfo(L"*.rmvb", FILE_ATTRIBUTE_NORMAL, &sfi, sizeof(SHFILEINFO), SHGFI_ICON | SHGFI_USEFILEATTRIBUTES);
HICON icon_handle = sfi.hIcon;
3、 提取其它size的图标,譬如: 48 * 48、256 * 256,则需要使用另外的方法
//获取48 * 48 或者 256 * 256 pixels icon
SHFILEINFO sfi;
ZeroMemory(&sfi, sizeof(SHFILEINFO));
::SHGetFileInfo(L"D:\\b.exe", FILE_ATTRIBUTE_NORMAL, &sfi, sizeof(SHFILEINFO), SHGFI_SYSICONINDEX);
HIMAGELIST* imageList = NULL;
//SHIL_EXTRALARGE获取48 * 48的图标, SHIL_JUMBO 获取256 * 256的图标。
HRESULT hResult = ::SHGetImageList(SHIL_EXTRALARGE , IID_IImageList, (void**)&imageList);
HICON icon_handle = NULL;
if (hResult == S_OK)
{
hResult = ((IImageList*)imageList)->GetIcon(sfi.iIcon, ILD_NORMAL, &icon_handle);
}
获取到HICON之后,可以使用DrawIcon API执行绘制,也可以使用AlphaBlend“重造*”执行绘制。如果要保存为文件,则构造一个ICON文件头,再把HICON转换为DIB数据拼在ICON文件头后边,注意:hbmColor和hbmMask这两个句柄的DIB数据都要有。我试过直接拿bmp位图数据修改:前头加上icon文件头,BITMAPINFOHEADER的biHeight *= 2,保存后各种size的图片都变残缺了(好像是被挖去了一个二维码),只有256*256的图片少部分像素丢失,显示稍微正常点,可能是因为256*256的图片mask部分用处不大的缘故,另外,还发现这样子的256*256 icon图片是留有透明通道的,这也说明,*.bmp位图文件中是可以有透明通道的,只是显示图片的工具把它忽略了。(2014.3.22 部分内容补充)
上边所说到的ico资源,都是在windows shell下能显示的图片,如果要提取没有显示的图片,则要利用资源枚举系列函数:EnumResourceNames、FindResouce、LoadResource、LockResource、CreateIconFromResourceEx。
本文所在:http://www.cnblogs.com/cswuyg/p/3603707.html
四、参考资料
1、ICON结构,很全面的知识:http://msdn.microsoft.com/en-us/library/ms997538.aspx
2、ICON发展历史系列,涉及一些MSDN没有谈到的细节:
3、HICON保存为*.ico文件,对于256格式的icon图片,保存时没有保存为png,其中一个答案的代码:“WriteFile(hFile, &padding, 4 - bmp.bmWidthBytes, &nWritten, 0);”有错误,应该是:“ WriteFile(hFile, &padding, 4 - (bmp.bmWidthBytes & 3), &nWritten, 0); ”,该答案对于低于8颜色深度的图片没有做处理,另一个使用GetDiBits实现的答案更好。
4、windows系统对ICON显示的各种选择:http://*.com/questions/3236115/which-icon-sizes-should-my-windows-applications-icon-include
5、ICO 文件格式以及发展历史:http://en.wikipedia.org/wiki/ICO_(file_format)
6、vista中的icons图标:http://msdn.microsoft.com/en-us/library/aa511280.aspx
7、不支持在应用程序中使用jpeg、png格式的解压,仅仅StretchDIBits、SetDIBitsToDevice两API支持直接向输出设备输出jpeg、png压缩格式数据,这是硬件支持的,使用时需要先判断硬件是否支持: http://msdn.microsoft.com/en-us/library/dd145023%28VS.85%29.aspx
8、获取exe *.ico文件中所有size的图片,批评者的观点很全面:http://*.com/questions/16330403/get-hbitmaps-for-all-sizes-and-depths-of-a-file-type-icon-c
9、总结了IOS、Windows上的各种icon size对比:http://www.visualpharm.com/articles/icon_sizes.html