ByteScope

ICO 檔案格式

Windows 圖示檔,內含一張或多張小尺寸點陣圖。

MIME 類型
image/x-icon
副檔名
.ico
分類
圖片

Magic number(檔案簽章)

0x0000000000000100····reserved + type 1 (icon)

關於 ICO 格式

ICO 會把同一個圖示的多種尺寸打包在一起,讓作業系統挑選合適的那一張;瀏覽器也用它來當 favicon。標頭是一個保留為零的字組,後面接著影像類型 1、影像張數,然後每張影像各一筆 16 位元組的目錄項目,記著那張影像的尺寸、色彩深度、位元組長度與在檔案中的位置。至於酬載本身,它們並不是什麼自己的格式:每一份不是拿掉檔案標頭的 Windows DIB,就是從 Windows Vista 之後可以整張內嵌進去的完整 PNG。所以 ICO 就是一堆別的影像前面擺了一份很小的索引,要讀它就得先讀目錄,再依每筆項目分頭處理。

檔頭結構,一個位元組一個位元組看

位移長度欄位意義
02Reserved必須是 00 00。加上後面那個類型欄位,這就是整個格式的 magic number 了,弱得不太尋常 — 四個位元組裡有兩個是零 — 所以偵測器還應該順便確認影像張數合不合理,以及第一筆目錄項目有沒有指在檔案裡面。
22Resource type01 00 代表圖示,02 00 代表游標。.ico 和 .cur 的差別就只有這一個位元組:標頭、目錄和酬載其他部分完全相同,而下面兩個欄位的意義會隨著它改變。
42Image count檔案裡有幾張影像,小端序,也就是緊接著會有幾筆 16 位元組的目錄項目。favicon 通常帶兩到三種尺寸;Windows 應用程式圖示常常有六種以上,從 16 像素一路到 256。
61Width of the first image單一個位元組,所以它能表達的最大值是 255 — 而這個格式處理 Windows Vista 引進的 256 像素圖示的辦法,是把 0 定義成 256。照字面把這個位元組讀進去的程式,會得到一張寬度為零的影像,通常再附贈一個除以零。
71Height of the first image編碼方式相同,0 代表 256 的規則也相同。跟 DIB 標頭不一樣的是,這裡是真正的顯示高度,不是內嵌點陣圖會宣稱的那個兩倍值,所以每一筆非 PNG 的項目,兩邊天生就對不起來。
81Palette entry count這張影像用了幾種顏色;當它是每像素 8 位元以上、因此沒有色盤時,就寫 0。現在的 32 位元圖示這裡一律是 0。
91Reserved必須是 0。這是整個檔案裡少數幾個完全沒有任何解讀意義的位元組,所以當你要判斷那四個很弱的 magic 位元組是不是真的代表圖示時,它是個不錯的額外檢查點。
102Colour planes, or cursor hotspot X在圖示裡這應該是 0 或 1,而讀取端兩種都收 — Pillow 寫 0,Windows 的工具鏈寫 1。在游標裡,同樣這兩個位元組是熱點的水平座標,也就是被當成指標尖端的那個像素。
122Bits per pixel, or cursor hotspot Y小端序的色彩深度:現在大家寫的 alpha 混合圖示是 32,老舊的色盤圖示是 8 或 4。在游標裡這兩個位元組改成熱點的垂直座標,所以把 .cur 改名成 .ico 之後,色彩深度會報出荒謬的數字。
144Size of the image data這筆項目的酬載有幾個位元組,不管那份酬載是沒有檔案標頭的 DIB 還是一整張 PNG。把它加上下一個欄位裡的位移,就是下一張影像的位置,所以整份目錄可以完全不解碼就走完一遍、再跟檔案大小對一次。
184Offset of the image data這筆項目的酬載從哪裡開始,從檔案開頭算起。兩張影像的圖示,第一筆是 38:六個位元組的標頭加上兩筆 16 位元組的項目。ICO 的排版全部都要從這些指標讀出來,不能用猜的。
2216Second directory entry這些項目是一個緊密排列的陣列,所以第二筆從 22 開始、第三筆從 38 開始,以此類推,欄位排法一模一樣。慣例上會由大到小排,但沒有任何東西強制,Windows 和瀏覽器也都不依賴這個順序。

怎麼跟長得像的格式分開

容易搞混的怎麼分
PNG現在的 ICO 通常把 PNG 整張裝進去,所以 hex dump 裡會出現 89 50 4E 47 0D 0A 1A 0A — 只是不在位移 0。兩張影像的 favicon 裡,它通常出現在位移 38,正好是第一筆目錄項目的位移欄位指到的地方。下任何結論之前先讀位移 0:00 00 01 00 是一個剛好裝著 PNG 的圖示,89 50 4E 47 則是一張被人命名成 .ico 的 PNG。
CUR位移 2 那個位元組,沒有別的。類型 1 是圖示、類型 2 是游標,後果是位移 10 那兩個位元組和位移 12 那兩個位元組不再是色彩平面數與位元深度,而變成熱點座標。所以把游標當成圖示打開,會報出像「每像素 12 位元」這種鬼數字。
BMP當某筆項目的酬載是點陣圖時,它並不是一個合法的 .bmp 檔案:十四位元組的 BITMAPFILEHEADER 被拿掉了,所以酬載一開頭就是 DIB 標頭長度 — 28 00 00 00 — 而且裡面的高度欄位是真實高度的兩倍,因為色彩資料後面還跟著一份同尺寸的 1 位元 AND 透明遮罩。把酬載挖出來改名成 .bmp,會得到一張兩倍高、圖片下方看得到遮罩雜訊的影像。
PNG/SVG favicon這不是位元組層級的判別法,而是大家真正需要的那個實務答案:瀏覽器多年前就接受用 link rel=icon 從任何路徑提供 PNG 或 SVG,甚至副檔名是 .ico、內容是 PNG 也照收。真正還非要 ICO 容器不可的是 Windows 自己 — 檔案總管、工作列和 .exe 資源 — 以及那些會盲目去抓 /favicon.ico 的老舊瀏覽器。

常見的壞法

  • 某筆目錄項目指到檔案結尾之外。因為每張影像是各自獨立取用的,結果會是局部壞掉:16 像素那張畫得出來、256 像素那張是空白的,或者 favicon 在某個瀏覽器看得到、在另一個看不到,端看它挑了哪個尺寸去要。檢查方式是算術題 — 每一筆的位移加上大小都不能超過檔案長度。
  • PNG 被改名成 favicon.ico。檔案本身沒有任何損壞,但位移 0 讀到的是 89 50 4E 47 而不是 00 00 01 00。現在的瀏覽器照樣全收,所以它在網路上好好的,一旦 Windows、安裝程式或比較舊的工具鏈想把它當圖示資源來讀,就當場壞掉。
  • 影像張數跟實際項目對不上。工具刪掉一種尺寸卻沒把位移 4 的張數減一,讀取端就會把第一份酬載的十六個位元組當成第十三筆目錄項目來解析,得到一筆位移離譜的項目 — 寫得馬虎的軟體不會報錯,而是直接當掉。
  • 256 像素的尺寸被寫成 256 而不是 0。它寫不進去:寬度和高度欄位各只有一個位元組。搞錯的寫入端會不小心截成 0,而沒實作「0 代表 256」規則的讀取端就會報出尺寸為零的影像,於是同一個檔案在 Windows 裡是張大圖示,在別的地方是不合法的檔案。
  • DIB 酬載的 AND 遮罩不見了或尺寸錯了。色彩資料解得很好,透明度是一團垃圾,得到的就是那種轉檔轉壞的圖示組裡一眼就認得出來的畫面:一個本來沒問題的圖示外面套著黑框或白框。

這個格式現在還在用嗎

ICO 今天還活著,基本上只有一個理由:不管 HTML 有沒有要求,瀏覽器仍然會去每個網站根目錄要 /favicon.ico,所以放一個上去,仍然是讓 log 少一筆 404、順便讓書籤列有東西可看的最便宜做法。除此之外它就是個 Windows 資源格式 — 檔案總管的圖示、捷徑圖示、工作列項目,以及編譯進 .exe 或 .dll 的圖示資源,用的都是同一套「目錄加酬載」結構。現代網頁做法早就走過去了:一個用 link rel=icon 宣告的 SVG 就能縮放到瀏覽器想要的任何尺寸,再配幾張 PNG 涵蓋吃不下向量的平台,所以現在多數網站只是丟一個兩種尺寸的小 .ico 當老舊 fallback,真正花力氣的是 PNG 和 SVG 版本。ICO 唯一還做得到、而那兩者做不到的事,是替每個尺寸包進真正不同的美術稿 — 當 16 像素的圖示需要是一張簡化過的圖、而不是縮小過的圖時,這件事就有差。

在這個站上打開 ICO 檔

這些全部在你的瀏覽器裡跑,檔案不會離開你的電腦。

相關格式

容易和 ICO 搞混的格式,可以比對它們的開頭位元組。

格式開頭位元組副檔名
PNG89 50 4E 47 0D 0A.png
BMP42 4D.bmp

常見問題

檔案的 magic number 是什麼?

檔案開頭的前幾個位元組,不管檔名寫什麼,它標明的是真正的格式。PNG 一定以 89 50 4E 47 開頭,ZIP 則是 50 4B 03 04。作業系統、瀏覽器和 file 指令讀的都是這幾個位元組,而不是相信副檔名 — 這就是為什麼把檔案改名並不會改變它實際上是什麼。

我要怎麼自己檢查 magic number?

在 macOS 或 Linux 上,xxd -l 16 檔名 會印出前 16 個位元組;在 Windows 上,PowerShell 的 Format-Hex -Path 檔名 -Count 16。或者把檔案丟進本站的檔案類型辨識工具,它會在本機讀取位元組並告訴你是什麼格式,不會上傳任何東西。

為什麼兩種格式的 magic number 會一樣?

因為其中一種是建立在另一種之上。docx、xlsx、pptx、apk、jar 與 epub 骨子裡都是 ZIP 壓縮檔,所以全都以 ZIP 的簽章開頭;要分辨它們得看裡面的項目。Java class 檔和 macOS 的通用二進位檔都以 CA FE BA BE 開頭,靠緊接其後的欄位區分。

有完全沒有 magic number 的檔案嗎?

有。XML、HTML、JSON、SVG、CSV 這類以文字為基礎的格式沒有固定的位元組簽章,所以改用結構來辨識:開頭的 <?xml 宣告、<svg 元素,或是 { 後面接著帶引號的鍵。這種偵測是合理推測而非確定,因此本參考頁會把這些格式標示為結構偵測。

magic number 相符就代表檔案安全嗎?

不代表。它告訴你的是格式,不是內容是否無害 — 一個貨真價實的 PDF 或 docx 仍然可能夾帶惡意內容。辨識真實類型是有用的第一道檢查,尤其在副檔名和位元組不一致的時候,但它不是惡意軟體掃描。

手上有檔案可能是 ICO?用檔案類型辨識工具查出來 — 它在本機讀取 magic number,不上傳任何東西。或者瀏覽所有格式。