Icons

1 篇文章 0 订阅
1 篇文章 0 订阅

Icons

译自 John Hornick同名文档

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
16 x 16 16 colors32 x 32  16 colors
32 x 32 16 colors
72 x 72  256 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

LocationIcon Size
DesktopShell Large
TitleBar of WindowsSystem Small
DialogSystem Large
Start MenuShell Small / Shell Large

虽然Windows对图标的大小没有限制,但常见的大小包括16、32和48个正方形像素。因此,鼓励开发人员在其图标资源中至少包含以下尺寸和颜色深度:

大小颜色深度
16 x 1616 colors
32 x 3216 colors
48 x 48256 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将选择标识符数值最低的图标。因此,为了确保应用程序使用特定图标,开发人员应确保满足以下两个标准:

  1. 该图标位于RC文件中所有其他图标之前。
  2. 如果图标已命名,则其名称按字母顺序排列在任何其他命名图标之前,否则其资源标识符在数字上小于任何其他图标。
4.1.2 Which Image?

一旦选择了RT_GROUP_ICON,则必须选择单个图标图像或RT_ICON资源,然后提取它。同样,如果所讨论的组仅存在一个RT_ICON资源,那么这个选择是很简单的。但是,如果组中存在多个图像,则应用以下选择规则:

  1. 选择大小最接近请求大小的图像。
  2. 如果存在两个或多个该尺寸的图像,则选择与显示器颜色深度匹配的图像。
  3. 如果没有与显示器的颜色深度完全匹配的图像,Windows将选择具有最大颜色深度的图像,但不会超过显示器的颜色厚度。
  4. 如果所有大小匹配的图像都超过显示器的颜色深度,则选择具有最低颜色深度的图像。
  5. Windows将8个或更多bpp的所有颜色深度视为相等。例如,在同一资源中拥有16x16 256色图像和16x16 16bpp图像是毫无意义的。Windows只需选择它遇到的第一个图像。
  6. 当显示处于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支持。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值