附錄五 圖示檔 ( *.ICO )


圖示檔介紹

從 Windows 作業系統的發展歷史來看,圖示 ( icon ) 佔有很重要的地位,它代表了親和操作界面的重要里程碑,俗話說「一圖解千文」就是這個道理。自 Windows 1.0 版以來, Windows 就已支援圖示,那時可以支援 32×32 的單色圖示,到了 Windows 3.0 則開始支援 256 色的圖示。自進入 Windows 9x/Me,已把 256 色的圖示作為內定的顏色,甚至可以支援到 65536 色 ( 也叫高彩,high color )。接下來的 Windows XP 系統更支援 16777216 色,甚至支援半透明。( 單色圖示其實是指黑白兩色,只需用 1 個位元就可以表示;256 色需要 8 個位元才能表示;65536 種顏色則須 16 位元的空間才可以顯示出來;16777216 色則需 24 位元,也叫全彩,true colors。我們也常用 1 位元圖示、8 位元圖示、24 位元圖分別示來表示單色圖示、256 色圖示、全彩圖示。請參考註一。)

Windows 所使用的圖示可分為四種大小:system small、system large、shell small、shell large,分別在不同的場合中使用:

微軟建議應用程式應該在資源檔中提供 256 色 48×48、16 色 32×32、16 色 16×16 三種圖示規格,系統能在這些圖示中挑出最適合的圖示,顯示在螢幕上。在 Windows 作業系統堛犒洏雂j多存放在三種檔案堙G可執行檔、動態連結程式庫與圖示檔堙A其副檔名分別是 EXE、DLL、ICO。一般而言,在 EXE 和 DLL 堛犒洏傸搣騤篞蔔﹞嚏A其形態與 ICO 一樣,但是分析 ICO 檔比 EXE 和 DLL 容易得多,所以在本附中,小木偶會分析 ICO 檔的結構,並著手製作一個動態連結程式庫,ICONS.DLL,其內有一個副程式可供其他程式呼叫,其名曰「hIconSaveAsIco」,可以在已知圖示代碼 ( hIcon ) 的情形下存成圖示檔。

不管是存放在哪一種檔案堛犒洏亶ㄛO正方形,其大小有 16×16、32×32、48×48、64×64、128×128、256×256 的大小,顏色可以是單色 ( 其實是黑白雙色 )、16 色、256 色及全彩四種。在此正方形內的區域包含兩個部分:透明部分與圖片部分。圖片部分以外的區域是透明的,可以把圖示背景顯現出來。假如您把圖示放在桌面上,那麼透明部分會顯現出桌面,而圖片部分則會擋住桌面而顯出圖示 ( 或稱為前景 ) 來。以下圖的 heart.ico 顯示在 Windows XP 風景桌面為例,正方形的白色外框是圖示區域,心形部分 ( 也就是前景 ) 則會擋住後面的背景;心形以外的區域是透明部分,可以看見懸崖,如下圖左。如果用滑鼠把心形圖示向右拖移到海面上,您就能很明顯看出來透明部分變成海面了。

這是怎麼辦到的呢?原來圖示其實包含了兩張圖片,這兩張圖片的大小相同,一張是單色的圖片是由 0 與 1 構成,稱為遮罩圖 ( icon bitmask bitmap ),透明的部分為 1,不透明的部分,也就是圖示的前景,是以 0 表示。而另一張稱為色彩圖 ( icon color bitmap ),透明的部分則是 0,前景不為零,為各種顏色。以上面心形圖示為例子,此 heart.ico 圖示檔堶惘釣漹i位元圖,一是遮罩圖,就是下圖左邊的黑白位元圖,黑色部分是前景,數值為 0;白色是透明部份,數值為 1。另一張是色彩圖,黑色是透明部份,數值為 0,其他彩色部分即為圖示的主體。您可以觀察到,色彩圖恰好可以嵌入遮罩圖中。

Windows 要顯示圖示時有兩個步驟,第一步,系統會先把桌面的背景圖片與遮罩圖做 AND 運算。讀者應知:任何數與 0 作 AND 運算後為 0,任何數與 1 作 AND 運算後仍為原數,前面所說得任何數即表示桌面的顏色,所以桌面圖片不管什麼顏色,與遮罩圖運算後,前景部分就變為 0,即黑色;而透明部分的遮罩圖為 1,故桌面圖片與其遮罩圖作 AND 運算後的結果還是桌面圖片,並沒有改變。您可以參考下圖,當第一個步驟完成後,會在圖示的前景部分留下黑色區域。

第二步是使第一步的結果與色彩圖作 XOR 運算。先說背景部份:讀者應知:0 與 0 作 XOR 運算得 0,1 與 0 作 XOR 運算得 1。換句話說,任何數與 0 作 XOR 的結果為原數值,所以做完第一個步驟後,透明部分為桌面圖案,可以是任何顏色 ( 或說任何數 ),與色彩圖的透明部分 ( 為 0 ) 作 XOR 後仍得到桌面的背景;而前景變為黑色 ( 即為 0 ),與色彩圖的圖示實體部分作 XOR 運算就會得到圖示實體。讀者可以參考下圖:

因為每個圖示的遮罩圖都會與背景作 AND 運算,所以遮罩圖也稱為「AND 位元圖」;同理,色彩圖會與第一個步驟後的結果作 XOR 運算,所以色彩圖也稱為「XOR 位元圖」。


圖示檔 ( *.ICO ) 結構

只含一個圖示的 ICO 檔

圖示檔可以包含一個或一個以上的圖示,如果圖示檔堛犒洏僆W過一個,每個圖示可以不一樣大,顏色數也可以不同,這種情形下較為複雜,稍後再說明。現在先說最簡單的圖示檔,這種圖示檔是只有一個圖示的,其結構如下:

名稱長度
(位元組)
結構體欄位說 明
ICONDIR6
ICONDIR           STRUC    
  idReserved      WORD   ?
  idType          WORD   ?
  idCount         WORD   ?
ICONDIR           ENDS
idReserved 必須是 0。
如果為圖示檔,副檔名是 ICO,idType 為 1;如果是游標檔,副檔名是 CUR,idType 為 2
idCount 是 ICO 檔案堜狶t圖示個數
ICONDIRENTRY16
ICONDIRENTRY      STRUC
  bWidth          BYTE   ?
  bHeight         BYTE   ?
  bColorCount     BYTE   ?
  bReserved       BYTE   ?
  wPlanes         WORD   ?
  wBitCount       WORD   ?
  dwBytesInRes    DWORD  ?
  dwImageOffset   DWORD  ?
ICONDIRENTRY      ENDS
bWidth、bHeight 分是圖示的寬度與高度,若為 0,表示圖示大小為 256×256
bColorCount 是顏色數,若等於 256 色或超過,bColorCount 為 0
bReserved 必須是 0
wPlanes 是顏色平面數
wBitCount 表示每一點包含幾個位元 ( bit ),如果是黑白圖示,每一點就只需佔用一個位元 ( 註一 )
dwBytesInRes 表示此圖示佔用多少位元組,包含 BITMAPINFOHEADER、調色盤、色彩圖、遮罩圖大小總和
dwImageOffset 表示此圖示由檔案起始處開始算起,哪一個位元組處開始,亦即偏移位址
BITMAPINFOHEADER40
BITMAPINFOHEADER  STRUC
  biSize          DWORD  ?
  biWidth         DWORD  ?
  biHeight        DWORD  ?
  biPlanes        WORD   ?
  biBitCount      WORD   ?
  biCompression   DWORD  ?
  biSizeImage     DWORD  ?
  biXPelsPerMeter DWORD  ?
  biYPelsPerMeter DWORD  ?
  biClrUsed       DWORD  ?
  biClrImportant  DWORD  ?
BITMAPINFOHEADER  ENDS
BITMAPINFOHEADER 與下面的 RGBQUAD 結構體合稱 BITMAP 結構體。在圖示檔堛 BITMAPINFOHEADER 結構體與 DIB ( 裝置無關的位元圖檔,一般常以 BMP 或 DIB 為副檔名 ) 圖檔中的 BITMAPINFOHEADER 結構體欄位相同,但是圖示檔的 BITMAPINFOHEADER 只用其中六個欄位:biSize、biWidth、biHeight、biPlanes、biBitCount、biSizeImage,其餘欄位都應該設為零。

biSize 是 BITMAPINFOHEADER 結構體大小,應為 40 個位元組
biWidth 是圖示寬度
biHeight 是圖示高度的兩倍,這是因為圖示檔中的位元資料是色彩圖加上遮罩圖。
biPlanes 是顏色平面數
biBitCount 是一點佔了幾個位元,biBitCount 與 biPlanes 的乘積即為 ICONDIRENTRY 結構體的 bColorCount
biSizeImage 是指 AND 位元圖與 XOR 位元圖大小總和
調色盤





RGBQUAD           STRUC
  rgbBlue         BYTE   ?
  rgbGreen        BYTE   ?
  rgbRed          BYTE   ?
  rgbReserved     BYTE   0
RGBQUAD           ENDS
調色盤是由數量不固定的 RGBQUAD 結構體所組成。對 16 色的圖示而言,是表示此圖示可以用 16 種顏色畫出來,而這 16 種顏色卻是可以任意選擇的,這些資料都記錄在 16 個 RGBQUAD 結構體堙C換言之,對 16 色的圖示而言,就有 16 個 RGBQUAD 結構體組成調色盤;對 256 色圖示而言,就有 256 個 RGBQUAD 結構體組成調色盤。第 0 個 RGBQUAD 結構體表示第 0 號顏色、第 1 個 RGBQUAD 結構體表示第 1 號顏色,而於其後的色彩圖則是這些結構體顏色的編號。對於全彩圖示而言,就不需要調色盤,直接以藍紅綠表示顏色。
color bitmap data
( 色彩圖位元資料 )






註四 單色、16 色、256 色的圖示,其色彩圖之值僅是調色盤之索引值,並非實際色彩值。例如 16 色的圖示其色彩圖某數值為 09H,並不是說 09H 所代表的顏色,而是說在調色盤第 09H 個 RGBQUAD 結構體所代表的顏色,以註二為例,應為紅色。只有全彩圖示的色彩圖之數值,才代表色彩值。

單色圖示每一點僅佔用 1 個位元,故一個位元組可以表示 8 個點。
16 色圖示每一點需 4 個位元表示,故一個位元組可以表示 2 個點。
256 色圖示每一點需 8 個位元表示,故一個位元組僅表示 1 個點。
全彩圖示每一點需 24 個位元或 32 個位元表示,故一個點卻要佔用 3∼4 個位元組。

不管哪一種圖示,顯示在螢幕上面的第一條線,放在 XOR 位元圖的最後面,請看註五的例子說明。如果是 biHeight 為負值,那麼才是最上面的一條線,才放在 ICO 檔的前面。
bitmask bitmap data
( 遮罩圖位元資料 )






註四 遮罩圖上的每一個點,只需用一個位元 ( bit ) 表示,但是遮罩圖的每一條線所佔用位元組個數必須是雙字組的整數倍。例如 16×16 圖示,每一條線僅需佔用 2 個位元組,因此還需要再加上兩個位元組,00 00 或 FF FF,以湊成雙字組,這樣雖然會增加圖示的體積,但是能加快顯示速度。跟色彩圖的排列情形一樣,遮罩圖顯示在螢幕的第一條線,放在 AND 位元圖的最後面,除非 biHeight 為負值。

註一:顏色數是 wPlanes 與 wBitCount 之乘積,與佔用幾個位元的關係如下表:
顏色數佔用位元數計算
2 ( 黑白圖示 )1 21
164 24
2568 28
16777216 ( 全彩 )24 或 32 224 或 232

註二:調色盤所佔的位元組個數依顏色數而定:

註三:色彩圖的大小 ( 佔用多少位元組 ) 與顏色數和圖示大小兩個因素有關,如右式:,均可以由 BITMAPINFOHEADER 結構體的欄位算出來。至於遮罩圖的大小比較麻煩,雖然遮罩圖的大小只和圖示的大小有關,而每一點只由一個位元就可以表示,但是遮罩圖的每一條橫線必須佔用雙字組的整數倍。舉例來說,如果是 16×16 圖示,每一條橫線有 16 個點,佔用 2 個位元組,所以需要再用 2 個 00 或 0FFH 的位元組填滿,成一個雙字組 ( 即 4 個位元組 ),共有 16 條橫線,因此 16×16 圖示的遮罩圖須佔用 64 個位元組 ( 即 16×4 )。再如 32×32 圖示的遮罩圖,每一橫線有 32 個點,恰好是 4 個位元組,即一個雙字組,共有 32 條橫線,所以佔用 128 個位元組 ( 即 32×4 )。又如 48×48 圖示,每一橫線有 48 點,佔用 6 個位元組,須再以 2 個 00 或 0FFH 的位元組填滿,成兩個雙字組,共有 48 條橫線,所以佔用 384 個位元組 ( 48×8 )。雖然看起來複雜,但寫成組合語言程式片段僅僅 6 行:( 這段程式在底下 ICONS.ASM 的第 127∼133 行 )

        ;求遮罩圖大小,並存於nBmMask
                mov     eax,bmih.biWidth
                add     eax,31
                shr     eax,5           ;130
                shl     eax,2
                mul     bmih.biHeight
                mov     nBmMask,eax

註四:色彩圖與遮罩圖不是由結構體組成的,而是每一點由數量不等的位元所組成。

註五:右圖是一個 16 色,16×16 大小的圖示,放大以後的圖形在底下。下圖中央的黃色部分的數字是遮罩圖的位元資料,每一點依據能顯示背景的透明部分為 1,或是顯示前景部分為 0。以第一條線為例,最左邊的那一點是透明的,接著的 12 點是黑色的,最右邊的 3 點是透明的,故得到其數值為「1000 0000 0000 0111」,換算成十六進位為「80 07」。但是為了配合存取速率,所以再添加兩個位元組「FF FF」,因此第一條線變成「80 00 FF FF」,此雙字組存在 ICO 檔的遮罩圖最後面,見下面「圖示內容列表」的棕色底紅色字部分。再以第 7 條線為例,只有最左邊的一點是透明,其餘均為圖示前景,故二進位表示為「1000 0000 0000 0000」,換算成十六進位為,「80 00」,在下面圖示內容列表的棕色底白色字部分。

在上圖最右側的天空色所表示的數字是色彩圖的位元資料,以第 7 條線為例,其顏色有灰色、暗綠色、白色,分別由調色盤中的第 7 個、第 2 個、第 F 個 RGBQUAD 結構體所表示,故其色彩圖為「07 2F 2F 2F FF 22 F2 27」,如下的暗紫色底淡黃色字的部分。又如第一條線,全為黑色,是調色盤中的第 0 個RGBQUAD 結構體,故八個位元組均為零,卻是安排在色彩圖位元資料的最後面,在下圖以暗紫色底橙色字表示。

下面圖示內容列表一開始黑灰底色的 6 個位元組是 ICONDIR 結構體,接著紅色底色的 16 個位元組是 ICONDIRENTRY 結構體,再接下來的暗綠底色部分是 BITMAPINFOHEADER 結構體,接著的藍色底色部分是調色盤,再來是暗紫色底色是色彩圖,最後棕色底色部分是遮罩圖。

圖示內容列表:
000h: 00 00 01 00 01 00 10 10 10 00 01 00 04 00 28 01
010h: 00 00 16 00 00 00 28 00 00 00 10 00 00 00 20 00
020h: 00 00 01 00 04 00 00 00 00 00 C0 00 00 00 00 00
030h: 00 00 00 00 00 00 10 00 00 00 00 00 00 00 00 00
040h: 00 00 00 00 80 00 00 80 00 00 00 80 80 00 80 00
050h: 00 00 80 00 80 00 80 80 00 00 80 80 80 00 C0 C0
060h: C0 00 00 00 FF 00 00 FF 00 00 00 FF FF 00 FF 00
070h: 00 00 FF 00 FF 00 FF FF 00 00 FF FF FF 00 00 00
080h: 00 00 00 00 00 07 07 FF FF FF FF FF FF F7 07 AA
090h: AF AA FF AA FF F7 07 AF AF AF AF AF AF F7 07 FA
0a0h: FF AA FF AA FF F7 07 FF FF FF FF FF FF F7 07 2F
0b0h: 2F 22 2F 2F FF 27 07 22 2F FF 2F 2F 2F 27 07 2F
0c0h: 2F F2 FF 2F 2F 27 07 2F 2F 2F FF 22 F2 27 07 F2
0d0h: FF 22 2F 2F FF 27 07 FF FF FF FF FF FF F7 07 FF
0e0h: FF FF FF FF 00 00 07 FF FF FF FF FF 7B 00 07 FF
0f0h: FF FF FF FF 70 00 00 00 00 00 00 00 00 00 80 00
100h: FF FF 80 00 FF FF 80 00 FF FF 80 00 FF FF 80 00
110h: FF FF 80 00 FF FF 80 00 FF FF 80 00 FF FF 80 00
120h: FF FF 80 00 FF FF 80 00 FF FF 80 00 FF FF 80 00
130h: FF FF 80 01 FF FF 80 03 FF FF 80 07 FF FF     

含有兩個或兩個以上圖示的 ICO 檔

一個圖示檔也可以同時包含好幾個顏色數、大小都不同的圖示,而在圖示檔一開始的 ICONDIR 結構體 ( idCount 欄位 ) 就標明了所含圖示個數。圖示檔接下來的內容就是為每個圖示建立起該圖示的資料,這些資料包含 ICONDIRENTRY 結構體、BITMAPINFOHEADER 結構體、調色盤、色彩圖及遮罩圖五部分。例如有一個圖示檔包含了三個大小、顏色數不同的圖示,它們在 ICO 檔案堛漲w排如下圖:

Windows XP 的 Alpha 通道

Windows XP 上市時,其漂亮的界面令人驚艷,它支援 128×128、16777216 色的全彩圖示。Windows XP 的圖示格式與以前幾乎相同,只是在全彩的圖示堨i以支援α通道。有了α通道的圖示,可以使線條看起來更平滑。

在 32 位元 ( 即全彩 ) 圖示,原本的色彩圖以 32 位元表示,前三個位元組分別代表藍、綠、紅三色的顏色「強度」,最高位址的位元組通常是不用,但是在 XP 圖示中賦予新的意義。它就是α通道,代表了 0 到 255 種不同的透明度,0 表示完全透明,亦即顯示背景顏色;255 表示不透明,顯示圖示顏色 ( 亦即前面三個位元組的顏色 )。圖示的三顏色與背景三顏色互相以下面公式混色:

alpha channel 混色

上式僅列出一個點的紅色分量,至於其他兩個分量的混色方式,也和上式類似。有了α通道表示透明程度以後,事實上,遮罩圖就沒有多大意義了,所以在 Windows XP 以後的系統,遮罩圖可以全部填零,但是為了和以前系統的相容性,這樣做並不好。


ICONS.DLL

製作 ICONS.DLL

底下小木偶將製作一個動態連結程式庫,ICONS.DLL,它含有一個可以把圖示代碼存成圖示檔的副程式,稱為「hIconSaveAsIco」。接著小木偶再撰寫一個程式,SAVETEST.ASM,可以讀取 1.ICO,以得到圖示代碼,然後存成 2.ICO 圖示檔,以驗證「hIconSaveAsIco」能正常運作。底下是 ICONS.ASM 的全部原始碼︰

        .586
        .MODEL  FLAT,STDCALL
        OPTION  CASEMAP:NONE

;假如需要用到包含檔,那麼可以在此處定義
INCLUDE         WINDOWS.INC
INCLUDE         GDI32.INC
INCLUDE         KERNEL32.INC
INCLUDE         USER32.INC
INCLUDELIB      GDI32.LIB
INCLUDELIB      KERNEL32.LIB
INCLUDELIB      USER32.LIB

ICONDIRENTRY    STRUC
  bWidth        BYTE    ?       ;015
  bHeight       BYTE    ?
  bColorCount   BYTE    ?
  bReserved     BYTE    ?
  wPlanes       WORD    ?
  wBitCount     WORD    ?       ;020
  dwBytesInRes  DWORD   ?
  dwImageOffset DWORD   ?
ICONDIRENTRY    ENDS

ICONDIR         STRUC           ;025
  idReserved    WORD    ?
  idType        WORD    ?
  idCount       WORD    ?
ICONDIR         ENDS

;*****************************************************************************************
.CODE
;-----------------------------------------------------------------------------------------
;進入/離開副程式的進入點
DLLEntry        PROC    hInstance,dwReason,dwReserved   ;035
                mov     eax,TRUE
                ret
DLLEntry        ENDP
;進入/離開副程式結束
;--------------------------------------------------------040------------------------------
;hicon_save_to_ico 把 hIcon 存成圖示檔(在已知圖示代碼的情形下,把hIcon存成ICO檔)
;輸入:hicon-圖示代碼
;      lpIcoFile-ICO檔名位址
hicon_save_to_ico       PROC    USES ebx esi edi hicon:HICON,lpIcoFile:LPSTR
                LOCAL   hFile,hDC,hMem:HANDLE           ;045
                LOCAL   nBmColor,nBmMask,nPalette:DWORD ;色彩圖、遮罩圖、調色盤大小
                LOCAL   ii:ICONINFO
                LOCAL   dwNumberOfBytesWritten:DWORD    ;呼叫WriteFile時,實際寫入的位元組
                LOCAL   bmih:BITMAPINFOHEADER
                LOCAL   ide:ICONDIRENTRY                ;050
                LOCAL   id:ICONDIR
                LOCAL   lpBmMask,lpBmih:LPSTR           ;遮罩圖與色彩圖位元資料存放位址
                INVOKE  CreateCompatibleDC,0
                or      eax,eax
                jz      error3                          ;055
                mov     hDC,eax
                INVOKE  CreateFile,lpIcoFile,GENERIC_WRITE,0,0,CREATE_ALWAYS,\
                                   FILE_ATTRIBUTE_NORMAL or FILE_FLAG_WRITE_THROUGH,0
                cmp     eax,INVALID_HANDLE_VALUE
                je      error2                          ;060
                mov     hFile,eax
        ;設定ICONDIR結構體
                mov     id.idReserved,0
                mov     id.idType,1
                mov     id.idCount,1                    ;065
        ;取得圖示的資料,主要是ii.hbmColor和ii.hbmMask兩個位元圖代碼
                INVOKE  GetIconInfo,hicon,ADDR ii
                or      eax,eax
                je      error1
                mov     edx,ii.hbmColor                 ;070
                mov     bmih.biSize,SIZEOF BITMAPINFOHEADER
                mov     bmih.biBitCount,0
                or      edx,edx
                jnz     not_mono_0                      ;若EDX為零,表示黑白圖示,改取得遮罩圖資料
                mov     edx,ii.hbmMask                  ;075
not_mono_0:     INVOKE  GetDIBits,hDC,edx,0,0,0,ADDR bmih,DIB_RGB_COLORS
                or      eax,eax
                jz      error1
        ;把bmih中的一些資料填入ide(包含biWidth、biHeight、bReserved、biPlanes
        ;、biBitCount、dwImageOffset)                  080
                mov     ide.dwImageOffset,SIZEOF ICONDIR+SIZEOF ICONDIRENTRY
                sub     edx,edx
                mov     eax,bmih.biWidth
                mov     ebx,bmih.biHeight
                movzx   ecx,bmih.biBitCount             ;085
                movzx   edi,bmih.biPlanes
                cmp     edx,ii.hbmColor
                jnz     not_mono_1
                shr     ebx,1                           ;若為單色圖示,此時所得的是遮罩圖資料
                mov     bmih.biHeight,ebx               ;,必須調整biHeight
not_mono_1:     mov     ide.bWidth,al
                mov     ide.bHeight,bl
                mov     ide.bReserved,dl
                mov     ide.wPlanes,di
                mov     ide.wBitCount,cx                ;095
        ;求出色彩圖(XOR位元圖)大小,存於nBmColor
                mul     ebx
                mul     ecx     ;EAX=每點佔用多少位元
                shr     eax,3   ;EAX=每點佔用多少位元組
                mov     nBmColor,eax                    ;100
        ;ide.dwBytesInRes=BITMAPINFOHEADER大小+調色盤大小+色彩圖大小+遮罩圖大小
                mov     ide.dwBytesInRes,eax
                add     ide.dwBytesInRes,SIZEOF BITMAPINFOHEADER
        ;求出顏色數,先求出顏色佔有(bmih.biBitCount*bmih.biPlanes)個位元
        ;但若(bmih.biBitCount*bmih.biPlanes)>8,則ide.bColorCount為0,
        ;否則把1向左移(bmih.biBitCount*bmih.biPlanes)次
                mov     eax,edi
                mul     ecx             ;ECX=顏色佔有的位元數
                mov     ecx,eax
                mov     ebx,1           ;110
                mov     eax,ebx
            .IF ecx<8
                shl     ebx,cl
            .ELSE
                sub     ebx,ebx         ;115
            .ENDIF
                mov     ide.bColorCount,bl
        ;求調色盤大小,並存於nPalette
            .IF cl==20h
                xor     eax,eax         ;120 全彩,沒有調色盤
            .ELSE
                shl     eax,cl          ;EAX=顏色數
                shl     eax,2           ;每個顏色佔用四個位元組
            .ENDIF
                mov     nPalette,eax    ;125
                add     ide.dwBytesInRes,eax    ;加上調色盤大小
        ;求遮罩圖大小,並存於nBmMask
                mov     eax,bmih.biWidth
                add     eax,31
                shr     eax,5           ;130
                shl     eax,2
                mul     bmih.biHeight
                mov     nBmMask,eax
                add     ide.dwBytesInRes,eax    ;ide.dwBytesInRes已計算完畢
        ;,至此已填完ICONDIRENTRY結構體,把ICONDIR、ICONDIRENTRY寫入圖示檔
                lea     ecx,dwNumberOfBytesWritten
                INVOKE  WriteFile,hFile,ADDR id,SIZEOF ICONDIR+SIZEOF ICONDIRENTRY,ecx,NULL
                or      eax,eax
                jz      error1          ;139

        ;配置記憶體空間,以存放BITMAPINFOHEADER、調色盤、色彩圖位元資料
                mov     ecx,80h
                add     ecx,ide.dwBytesInRes
                INVOKE  GlobalAlloc,GPTR,ecx
                or      eax,eax         ;145
                jz      error0
                mov     hMem,eax        ;記憶體區塊前半部放色彩圖位元資料,接著放遮罩圖(黑白圖示)的
                add     eax,nBmColor    ;位元資料,最後面放BITMAPINFOHEADER
                mov     lpBmMask,eax    ;lpBmMask是遮罩圖位元資料位址
                add     eax,nBmMask     ;150
                mov     lpBmih,eax      ;lpBmih是BITMAPINFOHEADER位址
                cld
                lea     esi,bmih
                mov     ecx,SIZEOF BITMAPINFOHEADER
                mov     edi,eax         ;155
                rep     movsb

        ;取得hbmColor的位元圖資料
                mov     edx,ii.hbmColor
                mov     ebx,hMem        ;160
                or      edx,edx
                jnz     not_mono_2
                mov     ebx,lpBmMask    ;若ii.hbmColor為零,表示黑白圖示,此時hMem所在的色彩圖位元資料
                mov     edx,ii.hbmMask  ;已備妥,接著not_mono_2處要讀取遮罩圖的位元資料 170
not_mono_2:     INVOKE  GetDIBits,hDC,edx,0,bmih.biHeight,ebx,lpBmih,DIB_RGB_COLORS
                or      eax,eax
                jz      error0
        ;調整bmih的資料:把biSizeImage設為hbmColor大小與hbmMask大小之和、把biHeight乘以2、其他欄位設為0
                mov     ebx,nBmColor
                add     ebx,nBmMask     ;170
                mov     edi,lpBmih
                xor     eax,eax
                mov     [edi+14h],ebx   ;biSizeImage=色彩圖長度+遮罩圖長度
                shl     DWORD PTR [edi+08h],1   ;使biHeight=biHeight*2
                mov     [edi+10h],eax   ;175 biCompression
                add     edi,18h
                mov     ecx,4           ;把biXPelsPerMeter、biYPelsPerMeter、biClrUsed、biClrImportant填入0
                rep     stosd
                mov     edx,SIZEOF BITMAPINFOHEADER
                lea     ecx,dwNumberOfBytesWritten
                add     edx,nPalette
                INVOKE  WriteFile,hFile,lpBmih,edx,ecx,NULL       ;把BITMAPINFOHEADER、調色盤寫入圖示檔
                or      eax,eax
                jz      error0          ;184

                cmp     ii.hbmColor,0
                jnz     not_mono_3
        ;如果是黑白圖示,只要把色彩圖位元資料與遮罩圖位元資料存入圖示檔即可
                lea     ecx,dwNumberOfBytesWritten
                mov     ebx,nBmColor    ;190
                add     ebx,nBmMask
                INVOKE  WriteFile,hFile,hMem,ebx,ecx,NULL
                or      eax,eax
                jz      error0
                jmp     exit_save       ;195

        ;非黑白圖示,取得hbmMask的BITMAPINFOHEADER
not_mono_3:     mov     edi,lpBmih
                mov     DWORD PTR [edi],SIZEOF BITMAPINFOHEADER
                mov     WORD PTR [edi+0eh],0    ;200
                INVOKE  GetDIBits,hDC,ii.hbmMask,0,0,0,edi,DIB_RGB_COLORS
                or      eax,eax
                jz      error0
        ;取得hbmMask的位元圖資料
                INVOKE  GetDIBits,hDC,ii.hbmMask,0,bmih.biHeight,lpBmMask,lpBmih,DIB_RGB_COLORS
                or      eax,eax
                jz      error0
                cmp     bmih.biBitCount,20h     ;檢查是否為32位元圖示,只有32位元圖示才有α通道
                jne     no_alpha
                mov     ebx,hMem                ;210
                sub     eax,eax
                mov     edi,ebx
                mov     esi,lpBmMask
        .WHILE ebx<esi
                cmp     BYTE PTR [ebx+3],0      ;215
                je      next_dword
                inc     eax
next_dword:     add     ebx,4
        .ENDW
                or      eax,eax                 ;220 若EAX=0,表示未使用α通道
                jnz     no_alpha                ;此32位元圖示已使用α通道,不須調整

        .WHILE esi<lpBmih
                lodsb
                mov     ecx,8                   ;225 每個遮罩圖位元資料位元組有8個點
next_pixel:     shl     al,1
                jc      transparency
                mov     BYTE PTR [edi+3],0ffh
transparency:   add     edi,4
                loop    next_pixel              ;230
        .ENDW

        ;把色彩圖位元資料寫入圖示檔
no_alpha:       mov     ecx,nBmColor
                add     ecx,nBmMask             ;235
                INVOKE  WriteFile,hFile,hMem,ecx,ADDR dwNumberOfBytesWritten,NULL
                or      eax,eax
                jnz     exit_save

error0:         INVOKE  GlobalFree,hMem         ;240
                INVOKE  DeleteObject,ii.hbmColor
                INVOKE  DeleteObject,ii.hbmMask
error1:         INVOKE  CloseHandle,hFile
                INVOKE  DeleteFile,lpIcoFile
error2:         INVOKE  DeleteDC,hDC            ;245
error3:         mov     eax,FALSE
                jmp     exit

        ;釋放記憶體區塊、關閉圖示檔、設備內容
exit_save:      INVOKE  GlobalFree,hMem         ;250
                INVOKE  CloseHandle,hFile
                INVOKE  DeleteDC,hDC
                mov     eax,TRUE
exit:           ret
hicon_save_to_ico       ENDP
;*****************************************************************************************
END             DLLEntry

模組定義檔,ICONS.DEF,的內容如下︰

EXPORTS
        hIconSaveAsIco=hicon_save_to_ico

VERSION 0.1

有了這兩個檔案之後,就可以開啟 Windows 系統的「命令提示字元」,輸入以下指令,以製作 ICONS.DLL 與 ICONS.LIB,供其他程式呼叫時使用。跟以前一樣,下面每一條指令後都需要按「Enter」鍵,輸入給電腦,小木偶以「[Enter]」表示︰( 如果您不太了解動態連結程式庫的話,請參考第 20 章 )

E:\HomePage\SOURCE\Win32\SEL_ICON>ml /c icons.asm [Enter]
Microsoft (R) Macro Assembler Version 6.14.8444
Copyright (C) Microsoft Corp 1981-1997.  All rights reserved.

 Assembling: icons.asm

E:\HomePage\SOURCE\Win32\SEL_ICON>link /DLL /SUBSYSTEM:WINDOWS /DEF:ICONS.DEF ICONS.OBJ [Enter]
Microsoft (R) Incremental Linker Version 5.12.8078
Copyright (C) Microsoft Corp 1992-1998. All rights reserved.

   Creating library ICONS.lib and object ICONS.exp

E:\HomePage\SOURCE\Win32\SEL_ICON>

最後再開啟文書處理軟體,輸入下面內容:

hIconSaveAsIco  PROTO   hicon:HICON,lpIcoFile:LPSTR

然後將它存成 ICONS.INC,這是用於在組合語言原始碼中是用來宣告「hIconSaveAsIco」的原型。到此,ICONS.DLL 算是製作完成了。

以 SAVETEST.ASM 測試 ICONS.DLL

製作完 ICONS.DLL 後,小木偶打算再撰寫一程式,SAVETEST.ASM,測試剛完成的 ICONS.DLL。底下是 SAVETEST.ASM 原始碼︰

                OPTION  CASEMAP:NONE
                .586
                .MODEL  FLAT,STDCALL

INCLUDE         WINDOWS.INC
INCLUDE         GDI32.INC
INCLUDE         KERNEL32.INC
INCLUDE         USER32.INC
INCLUDE         SHELL32.INC
INCLUDELIB      GDI32.LIB
INCLUDELIB      KERNEL32.LIB
INCLUDELIB      USER32.LIB
INCLUDELIB      SHELL32.LIB

INCLUDE         ICONS.INC
INCLUDELIB      ICONS.LIB

IDC_STATICICON  EQU     5000

;*****************************************************************************************
.CONST
szIconFile      BYTE    '1.ico',0
szSaveFile      BYTE    '2.ico',0
szDlgTemplate   BYTE    'IconSaveTest',0        ;對話盒面板名稱
;*****************************************************************************************
.DATA
hInstance       HINSTANCE        ?
hStatic         HWND             ?
hIcon           HICON            ?
;*****************************************************************************************
.CODE
;-----------------------------------------------------------------------------------------
DlgProc         PROC    hDlg:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM
.IF uMsg==WM_INITDIALOG
                INVOKE  GetDlgItem,hDlg,IDC_STATICICON
                mov     hStatic,eax
                INVOKE  ExtractIcon,hInstance,OFFSET szIconFile,0
                mov     hIcon,eax
                INVOKE  SendMessage,hStatic,STM_SETIMAGE,IMAGE_ICON,eax
                INVOKE  hIconSaveAsIco,hIcon,OFFSET szSaveFile

.ELSEIF uMsg==WM_CLOSE
                INVOKE  EndDialog,hDlg,NULL 

.ELSE
                mov     eax,FALSE
                ret
.ENDIF
                mov     eax,TRUE
                ret
DlgProc         ENDP
;-----------------------------------------------------------------------------------------
Start:          INVOKE  GetModuleHandle,NULL
                mov     hInstance,eax
                INVOKE  DialogBoxParam,hInstance,OFFSET szDlgTemplate,NULL,OFFSET DlgProc,NULL
                INVOKE  ExitProcess,eax
;*****************************************************************************************
END             Start

其資源描述檔,SAVETEST.RC,如下︰

#include "c:\masm32\include\resource.h"

#define IDC_STATICICON  5000

IconSaveTest    DIALOG  0,0,204,184
STYLE           DS_MODALFRAME|WS_VISIBLE|WS_CAPTION|WS_SYSMENU
CAPTION         "測試圖示存檔"
FONT            8,"Arial"
{
  CONTROL       "",IDC_STATICICON,"STATIC",SS_ICON,10,10,32,32
}

您可以在「命令提示字元」輸入下面指令組譯它︰

E:\HomePage\SOURCE\Win32\SEL_ICON>rc savetest.rc [Enter]

E:\HomePage\SOURCE\Win32\SEL_ICON>ml savetest.asm /link savetest.res [Enter]

Microsoft (R) Macro Assembler Version 6.14.8444
Copyright (C) Microsoft Corp 1981-1997.  All rights reserved.

 Assembling: savetest.asm
Microsoft (R) Incremental Linker Version 5.12.8078
Copyright (C) Microsoft Corp 1992-1998. All rights reserved.

/SUBSYSTEM:WINDOWS
"savetest.obj"
"/OUT:savetest.exe"
"savetest.res"

E:\HomePage\SOURCE\Win32\SEL_ICON>

您可以把任何一個圖示檔拷貝到此子目錄,並更名為「1.ico」,SAVETEST 會另存一個圖示檔案「2.ico」。


解說 hIconSaveAsIco

在解說 hIconSaveAs 之前,先介紹這個副程式會用到的 API︰ExtractIcon、GetIconInfo、GetDIBits 三個。當然還有一些 API 已經在前面介紹過的,就不囉唆了。

ExtractIcon、ExtractIconEx 與 LoadIcon API

在網路或您自己電腦上的圖示,大多存於 DLL、EXE、ICO 檔案堙A我們可以呼叫 ExtractIcon 或 ExtractIconEx 取得圖示代碼,這兩個 API 是包含在 SHELL32.DLL 堙A因此得先把 SHELL32.INC、SHELL32.LIB 加進來。底下是 ExtractIcon 的原型:

INVOKE  ExtractIcon,hInstance,lpszExeFileName,nIconIndex

先說 ExtractIcon,它可以從 lpszExeFileName 所指位址的檔案媯悃出圖示來,並賦予圖示代碼來表示此圖示,雖然 lpszExeFileName 看起來是執行檔的名稱,但是其實 DLL 或 ICO 檔也可以,除此之外,此位址的檔名需以 0 結尾。這些檔案有可能不只一個圖示,這時以 nIconIndex 指定要取出第幾個圖示,nIconIndex 由零開始;若 nIconIndex 為「-1」,則會使 ExtractIcon 傳回檔案中有幾個圖示。hInstance 是呼叫 ExtractIcon 的程式的執行實例。如果呼叫成功,則 ExtractIcon 返回圖示代碼;若檔案中無圖示,則傳回「0」;若 lpszExeFileName 所指位址的檔案不是 EXE、ICO、DLL 等檔案,則傳回「1」。

底下再談談 ExtractIconEx,其原型為:

INVOKE  ExtractIconEx,lpszFile,nIconIndex,phiconLarge,phiconSmall,nIcons

ExtractIconEx 可以從 lpszFile 所指定的檔名中,萃取出 nIcons 個圖示,然後把這些圖示代碼填入 phiconLarge、phiconSmall 所指的陣列中,前者是存放大圖示的陣列,後者存放小圖示陣列;至於由檔案中的第幾個圖示開始萃取,由 nIconIndex 指定,如果 nIconIndex 為「0」,表示萃取最前面的圖示;如果 nIconIndex 為「-1」且 phiconLarge、phiconSmall 均為零,表示取得檔案內有幾個圖示。ExtractIconEx 的返回值與 ExtractIcon 相同。

我們也可呼叫 LoadIcon 取得程式本身的圖示代碼:

INVOKE  LoadIcon,hInstance,lpIconName

hInstance 是程式的執行實例,lpIconName 是資源描述檔中的圖示識別碼,可能是數值或字串。如果要取得 Windows XP 系統內預先定義的圖示,這時候 hInstance 設為 NULL,而 lpIconName 可以是 IDI_APPLICATION、IDI_ASTERISK、IDI_EXCLAMATION、IDI_HAND、IDI_QUESTION、IDI_WINLOGO,結果分別為

GetIconInfo API

GetIconInfo 可以獲得圖示或游標的資料,僅包含色彩圖與遮罩圖的圖示代碼。GetIconInfo API 的原型為:

INVOKE  GetIconInfo,hIcon,piconinfo

GetIconInfo 會把 hIcon 的資料填入 piconinfo 所指的位址,此位址存放一個叫做 ICONINFO 的結構體,這個結構體的欄位是:

ICONINFO    STRUC
  fIcon     BOOL        ?   ;TRUE 表示圖示;FALSE 表示游標
  xHotspot  DWORD       ?   ;游標熱點的 X 座標;對圖示而言無意義,但通常是圖示寬度一半
  yHotspot  DWORD       ?   ;游標熱點的 Y 座標;對圖示而言無意義,但通常是圖示高度一半
  hbmMask   HBITMAP     ?   ;遮罩圖代碼
  hbmColor  HBITMAP     ?   ;色彩圖代碼
ICONINFO    ENDS

如果成功地呼叫完成,返回值為 TRUE,並填好 ICONINFO 內各欄位;如果呼叫失敗,返回 FALSE。如果 hIcon 是黑白圖示,那麼返回時,ICONINFO hbmColor 欄位是零,這是因為黑白圖示沒有色彩圖。程式成功呼叫 GetIconInfo 後,系統會建立色彩圖及遮罩圖代碼,因此如果程式不再使用這兩個位元圖時,應呼叫 DeleteObject 刪除它們。hIcon 也可以是系統已經預定好的圖示,例如 IDC_APPSTARTING、IDC_ARROW、IDC_CROSS、IDC_HAND、IDC_HELP 等等,如果是這樣的話,也能得到這些預定圖示的資料。

GetDIBits API

GetDIBits 可以獲得位元圖的位元資料和 BITMAPINFO 資料,其原型是

INVOKE  GetDIBits,hdc,hbmp,uStartScan,cScanLines,lpvBits,lpbi,uUsage

GetDIBits 有點兒複雜,可分兩方面說,一是取得位元資料與 BITMAPINFO;二是僅僅獲得 BITMAPINFO。

先說第一個功能。hdc 是設備內容;hbmp 是要取得位元資料的位元圖代碼;uStartScan 是要從哪一條橫線開始取得的位元資料,由零開始;cScanLines 是只要取得幾條橫線的位元資料;lpvBits 是指向儲存位元資料的記憶體位址;lpbi 是指向 BITMAPINFO 結構體所在位址;uUsage 是指定 BITMAPINFO 中調色盤的格式,可以使用下面兩個其中之一,DIB_PAL_COLORS 或 DIB_RGB_COLORS,前者表示調色盤由指向當前邏輯調色板的 16 位元索引值數組構成,後者表示調色盤由紅、綠、藍三原色直接值構成。如果呼叫成功,返回值為取得的橫線數目;若呼叫失敗,則返回 FALSE。如果程式想取得 hbmp 的位元資料,那麼要在呼叫前設定好 lpvBits,以存放位元資料,同時也要先設定好 lpbi 所指之 BITMAPINFOHEADER 的六個欄位 ( biSize、biWidth、biHeight、biPlanes、biBitCount 與 biSizeImage。BITMAPINFO 包含 BITMAPINFOHEADER 與調色盤兩部份 )。

GetDIBist 也可以僅僅獲得 hbmp 的 BITMAPINFO 資料,此時必須把 lpvBits 設為零,同時設定 lpbi 所指位址 BITMAPINFO 結構體的第一個欄位,也就是 biSize,biSize 是指 BITMAPCOREHEADER 結構體或 BITMAPINFOHEADER 結構體的大小,視您想獲得哪個結構體而定,一般是設定為 BITMAPINFOHEADER 的大小,以獲得較多的資料。呼叫 GetDIBits 後,系統就會在 lpbi 所指 BITMAPINFO 位址處填好位元圖的資料。如果呼叫前,您又讓 BITMAPINFOHEADER 結構體的 biBitCount 欄位為零,那麼 GetDIBits 就僅僅填好 BITMAPINFOHEADER 結構體,而不填調色盤與位元資料。返回時,如果呼叫成功,那麼 Windows 9x 會返回總共有幾條橫線;而 Windows NT/2K/XP 則返回非零值;若呼叫失敗,則 Windows 9x/NT/2K/XP 均返回 FALSE。

解說 hIconSaveAsIco 流程

hIconSaveAsIoc 是在已知圖示代碼的情況下,把圖示代碼存成僅含一個圖示的圖示檔,其步驟簡述如下︰

  1. 取得圖示代碼 ( 可以呼叫 ExtractIcon、ExtractIconEx、LoadIcon 等 API 取得圖示代碼 )。在 ICONS.ASM 堙A圖示代碼是由父程式傳來的。
  2. 以第一步取得的圖示代碼為參數,呼叫 GetIconInfo,取得色彩圖、遮罩圖代碼。( ICONS.ASM 的第 67 行 )
  3. 以「色彩圖代碼、lpvBits 等於 0」為參數,呼叫 GetDIBits ( 第 76 行 ),取得圖示的大小、顏色數等資料。計算 ICONDIRENTRY 各欄位之值 ( 第 79∼134 行 ),並寫入圖示檔堙C
  4. 配置記憶體,以存放 BITMAPINFO 與色彩圖及遮罩圖位元資料,並調整 BITMAPINFOHEADER,把 BITMAPINFOHEADER、調色盤寫入圖示檔堙C
  5. 呼叫 GetDIBits,取得遮罩圖位元資料,把色彩圖與遮罩圖的位元資料寫入到圖示檔堙A大功告成!

這個方法有一些限制,當 Windows 由 EXE、DLL 或 ICO 檔取得圖示時,如果原來的圖示大小、顏色數與作業系統不符合時,系統會自行調整到最適合的圖案輸出。例如現在 ( 民國 101 年 ) 常用的作業系統是 Windows XP,在工作區顯示的圖示是 32×32,全彩,但如果在 DLL 堛犒洏雂j小只有 16×16,顏色也只有 16 色,但是顯示在工作區的圖示仍是 32×32,全彩,這是系統自行調整了。如果有前輩高手知道如何存成原始的圖示檔,尚請不吝告知。小木偶在此多謝了。

底下再更詳細說明 ICONS.ASM。

因為 ICONS.ASM 只把從父程式傳來的圖示代碼存成一個圖示檔,因此 ICONDIR 的各欄位早就已經知道,分別是 0、1、1,程式的第 62∼65 行就是把這些欄位填好 0、1、1。因此第一步,在 ICONS.ASM 中其實是不需要的。

接著第二步是 ICONS.ASM 呼叫 GetIconInfo 取得色彩圖與遮罩圖代碼 ( 第 67 行 )。

再來判斷是否為黑白圖示 ( 第 73∼74 行 ),如果是黑白圖示,則以遮罩圖代碼呼叫 GetDIBits ( 第 75 行 ),否則以色彩圖代碼呼叫 GetDIBits,並在呼叫前使 lpvBits、hbmih.biBitCount 等於零,這樣就能於返回後,在 lpbi 所指的 BITMAPINFOHEADER 結構體位址得到色彩圖的寬、高、顏色等資料。ICONS.ASM 會依據這些資料計算出 ICO 檔案中的 ICONDIRENTRY 結構體 ( 第 79∼135 行 ),ICONDIRENTRY 結構體埵 bWidth、bHeight、bColorCount、bReserved、wPlanes、wBitCount、dwBytesInRes 和 dwImageOffset 共八個欄位,其中 bWidth、bHeight、bReserved、wPlanes、wBitCoun 五個欄位可以由色彩圖的 BITMAPINFOHEADER 直接獲得 ( 第 81∼95 行 )。計算 bColorCount 之值,在 ICONS.ASM 第 104∼117 行。計算 dwImageOffset 之值較為簡單,在程式的第 81 行。計算 dwBytesInRes 之值,較為複雜,因為 dwBytesInRes 是 BITMAPINFOHEADER大小、調色盤大小、色彩圖大小、遮罩圖大小之和,色彩圖大小在第 96∼100 行計算出來,調色盤大小在第 118∼125 行計算出來,遮罩圖大小在第 127∼133 行計算出來。第 134 行,已經把 dwBytesInRes 之值完全求出來了。最後再把 ICONDIR、ICONDIRENTRY 寫入圖示檔堙A見第 135∼137 行。這樣第三步驟就完成了。

第四步驟是配置記憶體,此記憶體區塊要容納 BITMAPINFOHEADER、調色盤、色彩圖與遮罩圖的位元資料 ( BITMAPINFO 包含 BITMAPINFOHEADER 與調色盤 )。取得這些資料當然是呼叫 GetDIBits,但是小木偶在此發現一個問題,那就是在 Windows XP 系統堙A如果使用 32 位元顏色的色彩圖代碼為參數,呼叫 GetDIBits,照理說在 BITMAPINFOHEADER 之後應該不會有調色盤才對,但是實際上返回後會在 BITMAPINFOHEADER 之後接著 3 個雙字組,位址由低而高分別代表藍、綠、紅三色,小木偶也不知是啥原因,不過圖示檔堣應含有這些資料,應予以除去;而 16、256 色圖示,則正常,會有調色盤。為了解決這個問題,小木偶在這配置的記憶體安排上不是照著 ICO 檔中的順序 ( BITMAPINFOHEADER、調色盤、色彩圖位元資料、遮罩圖位元資料 ),而是先存放色彩圖位元資料、遮罩圖位元資料,再存放 BITMAPINFOHEADER、調色盤。因為在第 118∼125 行已經正確的計算出調色盤大小,並存在 nPalette 變數堙A亦即如果是 32 位元顏色圖示,nPalette 為零;如果是其他顏色,nPalette 之值為正確調色盤大小,所以當要把 BITMAPINFOHEADER、調色盤寫入圖示檔堮 ( 第 179∼182 行 ),只需把要寫入的資料位址設在 BITMAPINFORHEADER 所在位址,寫入的資料長度是 BITMAPINFOHEADER 大小加上 nPalette 即可自動除去多餘的三個雙字組又不影響其他顏色圖示的調色盤。此外又為了容納這多餘的三個雙字組,所以又把這記憶體區塊多增加 80h 位元組 ( 程式第 142 行 )。配置記憶體時,所配置記憶體大小恰好為 ICONDIRENTRY 結構體的 dwBytesInRes 欄位,但因上述理由,再加上 80H 位元組,而所配置的記憶體是先由系統清除所有資料,亦即均變成零,這樣做是因為黑白圖示的色彩圖位元資料為零,ICONS 就不須額外處理了。

程式第 141∼151 行是計算色彩圖位元資料、遮罩圖的位元資料、BITMAPINFOHEADER 存放位址,分別以區域變數 hMem、lpBmMask、lpBmih 堙C然後把第 76 行所得的 BITMAPINFOHEADER 資料移到新配置記憶體區塊,lpBmih 所指位址 ( 第 152∼156 行 ),再呼叫 GetDIBits 就能夠得到色彩圖位元資料 ( 第 158∼165 行)。在呼叫 GetDIBits 之前,再檢查是否為黑白圖示,如果是的話則色彩圖位元資料已備妥,改以遮罩圖代碼呼叫 GetDIBits ( 第 161∼164 行 )。程式第 168∼178 行是調整色彩圖或遮罩圖的 BITMAPINFOHEADER,使之變為圖示檔的 BITMAPINFOHEADER,要調整的內容在程式第 168 行的註解已經說明了。然後再把 BITMAPINFO 寫入圖示檔 ( 第 182 行 ),到此步驟四算是完成了!

第五步,也是最後一步驟就是把色彩圖與遮罩圖的位元資料寫入圖示檔堙A此處分兩部份說明。先說黑白圖示,色彩圖並不用到,都是以零表示,在配置記憶體時 ( 第 144 行 ) 就已經設定為零,而第 165 行,也已讀取遮罩圖的位元資料了,所以只要指定好要寫入的資料位址及長度,寫入,就完成了 ( 第 186∼195 行 )。

再說如果不是黑白圖示,先呼叫 GetDIBits 取得遮罩圖的 BITMAPINFOHEADER ( 第 201 行 ),等填好後,再呼叫一次 GetDIBits,就可以得到遮罩圖位元資料 ( 第 205 行 )。得到遮罩圖位元資料後,再與色彩圖位元資料一起寫入圖示檔 ( 第 233∼236 行 ),就大功告成。

不過小木偶以「Junior Icon Editor」這套圖示編輯器測試「SAVEICON」所製造出來的圖示檔發現,假如 1.ICO 是 16 色圖示,經 XP 的裝置內容轉換後變成的 32 色圖示,無法正常顯示;但另一套軟體,「IcoFX」,卻無此問題。究其原因,原來是「Junior Icon Editor」會使用α通道。所以在第 208∼209 行用來檢查是否為 32 色圖示,如果不是則不須使用α通道;如果是 32 色圖示,再檢查使否已使用α通道 ( 第 210∼221 行 ),如果已使用也不須調整,若不使用α通道才須調整,直接跳躍至把色彩圖位元資料寫入圖示檔堛熊{式片段。小木偶檢查色彩圖中每個點的最後一個位元組,如果均為 0,表示沒使用α通道,這時再檢查遮罩圖的每個點,如果是前景圖 ( 此位元為 1 ),則把α通道設為 0FFH,表示不透明;否則設為 0,表示透明 ( 第 225∼228 行 )。