Icons
译自 John Hornick同名文档
Iocns
1.Abstract
本文详细介绍了Windows中图标的格式和使用。涵盖以下主题;ICO、DLL和EXE文件中图标资源的格式,内存中图标图像的格式,Windows对图标的使用,Windows从图标资源中选择图标图像,以及操作图标图像所提供的API。为了跟上讨论,读者应该熟悉设备无关位图(DIB)及其格式。有关DIB的更多信息,请参考以下来源:
BITMAPINFO结构的文档
知识库文章Q81498示例:DIB及其使用
知识库文章Q94326示例:每像素16位和32位位图格式
处理本文中某些主题的示例代码可在IconPro项目的平台SDK示例树中找到。
2.Introduction
图标种类繁多,有很多尺寸和颜色深度。单个图标资源、ICO文件或EXE或DLL文件中的图标资源可以包含多个图标图像,每个图标图像具有不同的大小和/或颜色深度。Windows 95和Windows NT的未来版本(从这里起统称为“Windows”)根据图标的使用上下文从资源中提取适当的大小/颜色深度图像。Windows还提供了一系列用于访问和显示图标和图标图像的功能。
3.What’s in an Icon?
图标资源可以包含多个图标图像。例如,一个图标资源在这种情况下,单个.ICO文件可以包含多种大小和颜色深度的图像:
16 x 16 16 colors
32 x 32 16 colors
72 x 72 256 colors
3.1 The ICO File
通常具有ICO扩展名的图标文件包含一个图标资源。鉴于图标资源可以包含多个图像,因此文件以图标目录开始并不奇怪:
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;
idCount成员指示图标资源中存在多少图像。Identiries数组的大小由idCount确定。文件中的每个图标图像都有一个图标索引,提供了有关其在文件中的位置、大小和颜色深度的详细信息。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;
对于每个ICONDIRENTRY,该文件包含一个图标图像。dwBytesInRes成员指示图像数据的大小。该图像数据可以从文件开头的dwImageOffset字节中找到,并以以下格式存储:
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成员的形式为DIB BITMAPINFOHEADER。仅使用以下成员:biSize、biWidth、biHeight、biPlanes、biBitCount、biSizeImage。所有其他成员必须为0。biHeight成员指定XOR和AND掩码的组合高度。icHeader的成员以与BITMAPINFOHEADER结构定义CF_DIB格式DIB相同的方式定义ICONIMAGE结构的其他元素的内容和大小。
icColors成员是一个RGBquad数组。此数组中的元素数通过检查iHeader成员来确定。
icXOR成员包含图像的XOR掩码的DIB位。此数组中的字节数是通过检查icHeader成员确定的。XOR掩码是图像的颜色部分,并且在应用and掩码之后使用XOR操作应用到目的地。
icAND成员包含单色和掩码的位。该数组中的字节数是通过检查icHeader成员并假设1bpp来确定的。此位图的尺寸必须与XOR掩码的尺寸相同。使用AND操作将AND掩码应用于目标,以在应用XOR掩码之前保留或移除目标像素。
注:
icHeader结构的双高度成员表示XOR和AND掩码的组合高度。在使用该数字执行XOR或AND掩码的计算之前,请记住将其除以2。还要记住,AND掩码是单色DIB,颜色深度为1bpp。
以下是用于读取.ICO文件的不完整代码片段:
// We need an ICONDIR to hold the data
pIconDir = malloc( sizeof( ICONDIR ) );
// Read the Reserved word
ReadFile( hFile, &(pIconDir->idReserved), sizeof( WORD ), &dwBytesRead, NULL );
// Read the Type word - make sure it is 1 for icons
ReadFile( hFile, &(pIconDir->idType), sizeof( WORD ), &dwBytesRead, NULL );
// Read the count - how many images in this file?
ReadFile( hFile, &(pIconDir->idCount), sizeof( WORD ), &dwBytesRead, NULL );
// Reallocate IconDir so that idEntries has enough room for idCount elements
pIconDir = realloc( pIconDir, ( sizeof( WORD ) * 3 ) +
( sizeof( ICONDIRENTRY ) * pIconDir->idCount ) );
// Read the ICONDIRENTRY elements
ReadFile( hFile, pIconDir->idEntries, pIconDir->idCount * sizeof(ICONDIRENTRY),
&dwBytesRead, NULL );
// Loop through and read in each image
for(i=0;i<pIconDir->idCount;i++)
{
// Allocate memory to hold the image
pIconImage = malloc( pIconDir->idEntries[i].dwBytesInRes );
// Seek to the location in the file that has the image
SetFilePointer( hFile, pIconDir->idEntries[i].dwImageOffset,
NULL, FILE_BEGIN );
// Read the image data
ReadFile( hFile, pIconImage, pIconDir->idEntries[i].dwBytesInRes,
&dwBytesRead, NULL );
// Here, pIconImage is an ICONIMAGE structure. Party on it :)
// Then, free the associated memory
free( pIconImage );
}
// Clean up the ICONDIR memory
free( pIconDir );
完整的代码可以在icons.c模块中找到,在名为ReadIconFromICOFile的函数中。
3.2 DLL and EXE Files
图标也可以存储在.DLL和.EXE文件中。用于在.EXE和.DLL文件中存储图标图像的结构与在.ICO文件中使用的结构仅略有不同。
与ICO文件中的ICONDIR数据类似的是RT_GROUP_ICON资源。实际上,使用资源编译器/链接器为绑定到EXE或DLL的每个ICO文件创建一个RT_GROUP_ICON资源。RT_ GROUP_ ICON资源只是一个GRPICONDIR结构:
// #pragmas are used here to insure that the structure's
// packing in memory matches the packing of the EXE or DLL.
#pragma pack( push )
#pragma pack( 2 )
typedef struct
{
WORD idReserved; // Reserved (must be 0)
WORD idType; // Resource type (1 for icons)
WORD idCount; // How many images?
GRPICONDIRENTRY idEntries[1]; // The entries for each image
} GRPICONDIR, *LPGRPICONDIR;
#pragma pack( pop )
idCount成员指示图标资源中存在多少图像。Identiries数组的大小由idCount确定。资源中的每个图标图像都有一个GRPICONDIRENTRY,提供关于其大小和颜色深度的详细信息。GRPICONDIRENTRY结构定义为:
#pragma pack( push )
#pragma pack( 2 )
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
WORD wPlanes; // Color Planes
WORD wBitCount; // Bits per pixel
DWORD dwBytesInRes; // how many bytes in this resource?
WORD nID; // the ID
} GRPICONDIRENTRY, *LPGRPICONDIRENTRY;
#pragma pack( pop )
dwBytesInRes成员指示nID成员引用的RT_ ICON资源的总大小。nID是可以传递给FindResource、LoadResource和LockResource的RT_。
以下是从.DLL或.EXE文件读取图标的不完整代码段:
// Load the DLL/EXE without executing its code
hLib = LoadLibraryEx( szFileName, NULL, LOAD_LIBRARY_AS_DATAFILE );
// Find the group resource which lists its images
hRsrc = FindResource( hLib, MAKEINTRESOURCE( nId ), RT_GROUP_ICON );
// Load and Lock to get a pointer to a GRPICONDIR
hGlobal = LoadResource( hLib, hRsrc );
lpGrpIconDir = LockResource( hGlobal );
// Using an ID from the group, Find, Load and Lock the RT_ICON
hRsrc = FindResource( hLib, MAKEINTRESOURCE( lpGrpIconDir->idEntries[0].nID ),
RT_ICON );
hGlobal = LoadResource( hLib, hRsrc );
lpIconImage = LockResource( hGlobal );
// Here, lpIconImage points to an ICONIMAGE structure
完整的代码可以在IconPro.c模块中找到,在名为ReadIconFromEXEFile的函数中。
3.3 In Memory
处理内存中的图标资源时,格式与.EXE和.DLL文件中使用的格式相同。诸如CreateIconFromResource之类的函数期望传递一个ICONIMAGE结构。这非常方便,因为FindResource、LoadResource和LockResource可以用于加载该格式的RT_ICON资源 。
HICON句柄是单个图标图像或RT_ICON资源的句柄。在以前版本的Windows中,HICON映像的大小可以通过调用带有SM_CYICON和SM_CXICON标志的GetSystemMetrics来确定。然而,现在可以为非标准尺寸的图标设置HICON手柄。HICON图标始终具有与显示设备相同的颜色格式。有关如何使用HICON句柄处理不同大小和颜色深度的图标的更多详细信息,请参阅下面的函数讨论。
4. When in Windows
在Windows中,系统保持两种图标大小的概念,即小图标和大图标。此外,shell还具有大小图标的概念。这意味着Windows总共知道四种不同的图标大小:System Small、System Large、Shell Small和Shell Large。
系统小尺寸是从窗口标题的大小导出的。可以从“显示属性”对话框中的“外观”选项卡调整标题大小。对标题大小的调整立即反映在系统小图标大小中。通过使用SM_ CXSMICON和SM_CYSMICON参数。
系统大尺寸由视频驱动程序定义,因此不能动态更改。通过使用SM_ CXICON和SM_CYICON参数调用GetSystemMetrics查询。
Shell小尺寸由Windows定义,目前Windows不支持更改此值,目前也没有直接查询此值的方法。
Shell大尺寸存储在注册表中的以下项下:
HKEY_CURRENT_USER\Control Panel\desktop\WindowMetrics\Shell Icon Size
可以通过修改注册表或从“显示属性”对话框中的“外观”选项卡更改外壳大尺寸,该选项卡允许值从16到72。以下是通过访问注册表更改Shell大图标大小的代码示例:
DWORD SetShellLargeIconSize( DWORD dwNewSize )
{
#define MAX_LENGTH 512
DWORD dwOldSize, dwLength = MAX_LENGTH, dwType = REG_SZ;
TCHAR szBuffer[MAX_LENGTH];
HKEY hKey;
// Get the Key
RegOpenKey( HKEY_CURRENT_USER, "Control Panel\\desktop\\WindowMetrics", &hKey);
// Save the last size
RegQueryValueEx( hKey, "Shell Icon Size", NULL, &dwType, szBuffer,
&dwLength );
dwOldSize = atol( szBuffer );
// We will allow only values >=16 and <=72
if( (dwNewSize>=16) || (dwNewSize<=72) )
{
wsprintf( szBuffer, "%d", dwNewSize );
RegSetValueEx( hKey, "Shell Icon Size", 0, REG_SZ, szBuffer,
lstrlen(szBuffer) + 1 );
}
// Clean up
RegCloseKey( hKey );
// Let everyone know that things changed
SendMessage( HWND_BROADCAST, WM_SETTINGCHANGE, SPI_SETICONMETRICS,
(LPARAM)("WindowMetrics") );
return dwOldSize;
#undef MAX_LENGTH
}
Table 1. Where Windows Uses Different Sized Icons
Location | Icon Size |
---|---|
Desktop | Shell Large |
TitleBar of Windows | System Small |
Dialog | System Large |
Start Menu | Shell Small / Shell Large |
虽然Windows对图标的大小没有限制,但常见的大小包括16、32和48个正方形像素。因此,鼓励开发人员在其图标资源中至少包含以下尺寸和颜色深度:
大小 | 颜色深度 |
---|---|
16 x 16 | 16 colors |
32 x 32 | 16 colors |
48 x 48 | 256 colors |
4.1 Choosing an Icon
当Windows准备显示图标(例如桌面快捷方式)时,它必须解析.EXE或.DLL文件并提取相应的图标图像。该选择是一个两步过程,从选择适当的RT_GROUP_ICON开始,到从RT_GROUP_ICON中选择适当的RT_ICON为止。
4.1.1 Which Icon?
如果.EXE或.DLL文件只有一个RT_GROUP_ICON资源,则第一步很简单,Windows仅使用该资源。但是,如果文件中存在多个此类组资源,Windows必须决定使用哪一个。Windows NT只需选择应用程序的RC脚本中列出的第一个资源。另一方面,Windows95的算法是选择按字母顺序排列的第一个组图标(如果存在)。如果一个这样的组资源不存在,Windows将选择标识符数值最低的图标。因此,为了确保应用程序使用特定图标,开发人员应确保满足以下两个标准:
- 该图标位于RC文件中所有其他图标之前。
- 如果图标已命名,则其名称按字母顺序排列在任何其他命名图标之前,否则其资源标识符在数字上小于任何其他图标。
4.1.2 Which Image?
一旦选择了RT_GROUP_ICON,则必须选择单个图标图像或RT_ICON资源,然后提取它。同样,如果所讨论的组仅存在一个RT_ICON资源,那么这个选择是很简单的。但是,如果组中存在多个图像,则应用以下选择规则:
- 选择大小最接近请求大小的图像。
- 如果存在两个或多个该尺寸的图像,则选择与显示器颜色深度匹配的图像。
- 如果没有与显示器的颜色深度完全匹配的图像,Windows将选择具有最大颜色深度的图像,但不会超过显示器的颜色厚度。
- 如果所有大小匹配的图像都超过显示器的颜色深度,则选择具有最低颜色深度的图像。
- Windows将8个或更多bpp的所有颜色深度视为相等。例如,在同一资源中拥有16x16 256色图像和16x16 16bpp图像是毫无意义的。Windows只需选择它遇到的第一个图像。
- 当显示处于8bpp模式时,Windows将首选16色图标而不是256色图标,并将使用系统默认调色板显示所有图标。
5. The Icon API
在处理图标时,开发人员可以选择操纵原始资源字节,或者让Windows使用HICON句柄处理低级细节。处理原始资源字节的优势在于获得控制,而使用HICON句柄的优势在于简单。对于大多数目的,HICON接口就足够了。很可能只有在开发图标处理程序时才需要处理原始资源字节。
5.1 Raw Resource Bytes
当然,用于操作资源FindResource、LoadResource和LockResource的标准Windows API函数可以用于处理图标资源。
可以使用EnumResourceNames,传入RT_GROUP_ICON来查找可用的组图标资源。一旦选择了适当的组资源,就可以使用FindResource、LoadResource和LockResource加载它。这将产生指向GRPICONDIR结构的指针。
Identiries数组是在所需颜色深度和大小上搜索匹配的数组。然后将该数组元素的nID成员用作FindResource的参数,传入RT_ICON。然后,LoadResource和LockResource生成指向该图标图像的ICONIMAGE结构的指针。
为了允许Windows执行颜色深度和大小选择,可以将GRPICONDIR结构传递给LookUpIconIdFromDirectory或LookUpIconIdFromDirectoryEx。这两个函数都返回一个id,可以与RT_ICON和FindResource一起使用,后者提供了一种指定所需大小以匹配的方法。
ICONIMAGE结构包含指向掩码的DIB位的指针。这些指针可以在DIB函数中用于直接操作。ICONIMAGE结构也方便地适合传递给CreateIconFromResource或CreateIContFromResourceEx,以生成HICON句柄。两个函数中的前一个函数创建一个系统大尺寸的图标。后者提供了一种指定所需大小的方法,Windows执行适当的转换。
5.2 HICON Handles
HICON句柄是单个图标图像的句柄。这类似于单个RT_ICON资源。图像使用设备相关位图(DDB)在内部存储。这意味着所有HICON图标与显示设备具有相同的颜色格式。图标的大小取决于其原点和系统定义的图标大小。
可用的图标处理功能可以分为两组:处理系统大尺寸图标的功能和处理任意尺寸图标的程序。当系统仅定义一个图标大小时,仅处理系统大尺寸图标的函数通常是16位天数的遗留函数。较新的函数,即处理任意大小图标的函数,接受所需图标大小作为参数。
5.2.1One Size Fits All
最初的图标处理功能是为仅定义一个图标大小的系统设计的。因此,大多数功能都不知道存在多个图标大小的可能性,并假设所有图标都是系统大尺寸。
LoadIcon、ExtractIcon和DrawIcon属于此类。LoadIcon和ExtractIcon始终搜索系统大尺寸的匹配项。如果找不到精确匹配,这两个函数会将最接近的匹配拉伸到该大小。它们总是返回系统大尺寸的图标。类似地,DrawIcon总是以系统大尺寸绘制图标。如果将不同大小的图标传递给DrawIcon,它将被拉伸并以系统大尺寸显示。
CreateIconFromResource也表现出这种行为。它返回一个系统大尺寸图标的句柄,根据需要扩展它传递的RT_ICON资源。
5.2.2 To Each Their Own
既然Windows能够处理不同大小的图标,就添加了新的功能来处理它们。在某些情况下,扩展了旧函数,并在其名称中添加了“Ex”。在其他情况下,添加了全新的功能。最终的结果是,Windows API现在完全支持不同大小的图标。
有几种不同的功能可用于将HICON手柄连接到不同大小的图标。LoadImage可用于从EXE或DLL文件中提取图标,而无需手动加载资源字节。如果已加载资源字节,则CreateIconFromResourceEx可用。
CreateIcon和CreateIoninDirect,即使它们的根在16位的土地上,也有助于创建不同大小的图标。CreateIcon接受所需的宽度和高度作为参数,而CreateIoninDirect基于ICONINFO参数中的位图创建图标。请注意,这两个函数都适用于DDB,而不是DIB。
SHGetFileInfo还可以用于从文件中获取图标,提供shell将为文件显示的图标。SHGetFileInfo适用于任何类型的文件,可以提取四种图标大小中的任何一种,如下所示:
// Load a System Large icon image
SHGetFileInfo( szFileName, 0, &shfi, sizeof( SHFILEINFO ),
SHGFI_ICON | SHGFI_LARGEICON);
// Load a System Small icon image
SHGetFileInfo( szFileName, 0, &shfi, sizeof( SHFILEINFO ),
SHGFI_ICON | SHGFI_SMALLICON);
// Load a Shell Large icon image
SHGetFileInfo( szFileName, 0, &shfi, sizeof( SHFILEINFO ),
SHGFI_ICON | SHGFI_SHELLICONSIZE);
// Load a Shell Small icon image
SHGetFileInfo( szFileName, 0, &shfi, sizeof( SHFILEINFO ),
SHGFI_ICON | SHGFI_SHELLICONSIZE | SHGFI_SMALLICON);
给定HICON手柄,DrawIconEx可用于以正常尺寸、系统大尺寸或任何其他尺寸显示:
// Draw it at its native size
DrawIconEx( hDC, nLeft, nTop, hIcon, 0, 0, 0, NULL, DI_NORMAL );
// Draw it at the System Large size
DrawIconEx( hDC, nLeft, nTop, hIcon, 0, 0, 0,
NULL, DI_DEFAULTSIZE | DI_NORMAL );
// Draw it at some other size (40x40 in this example)
DrawIconEx( hDC, nLeft, nTop, hIcon, 40, 40, 0, NULL, DI_NORMAL );
请注意,DrawIconEx将根据需要拉伸图标,使其符合所需的输出大小。
5.2.3 What’s in There?
Windows API提供了一个函数,用于根据图标的HICON句柄确定图标的特性。此函数是GetIconInfo。GetIconInfo用与HICON相关的信息填写ICONINFO结构。ICONINFO结构包含以下信息:
typedef struct _ICONINFO { // ii
BOOL fIcon; // TRUE for icon, FALSE for cursor
DWORD xHotspot; // the x hotspot coordinate for cursor
DWORD yHotspot; // the y hotspot coordinate for cursor
HBITMAP hbmMask; // handle to monochrome AND mask bitmap
HBITMAP hbmColor; // handle to device dependent XOR mask bitmap
} ICONINFO;
给定这些信息,应用程序可以计算将图标写入文件所需的信息。通过调用此结构中两个位图上的GetDIBits,可以获得AND掩码和XOR掩码DIB位。
6. A Word on Cursors
光标与图标非常相似。事实上,通过只更改IconPro源代码中的一行,该示例可以读取.CUR文件。IconPro目前正在测试ICONDIR结构的idType成员,以确保该文件是图标文件。可以放宽此检查,以允许游标(2)的类型。此外,在大多数图标函数中,HCURSOR句柄可以与HICON句柄互换使用。
7. Conclusion
尽管图标规范长期以来一直能够处理奇数大小和颜色深度的图标,但直到最近,Windows才对此类图像提供了固有的支持。开发人员现在可以选择直接处理图标位,或者允许Windows处理所有细节。Windows甚至为加载和显示非标准大小和不同颜色深度的图标提供了API支持。