Icon資源詳解[1]


     本文分享&備忘最近了解到的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實現的答案更好。
5、ICO 文件格式以及發展歷史:http://en.wikipedia.org/wiki/ICO_(file_format)
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://stackoverflow.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
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM