Apri un file ICO in un editor esadecimale. I primi sei byte:
00 00 01 00 03 00
Questo è l'intero header del file. 00 00 è il campo Reserved — sempre zero. 01 00 è il campo Type — 1 significa che questo è un file icona (i cursor usano 2). 03 00 è il campo Count — little-endian per 3, il che significa che questo ICO contiene tre immagini separate. Sei byte per descrivere un container che ospita tre file immagine indipendenti. Il resto del file è costituito dai metadati di quelle immagini, seguiti dai dati immagine veri e propri.
ICO non è né un algoritmo di compressione né uno spazio colore: è semplicemente un container, il formato contenitore più essenziale e longevo ancora in uso quotidiano su ogni PC Windows e su tutti i principali browser.
Da Dove Viene ICO
Microsoft introdusse ICO con Windows 1.0 nel 1985. L'ecosistema PC nel 1985 non aveva alcun concetto di file immagine standardizzato. Non esisteva JPEG, non esisteva PNG, non esisteva GIF. I dati bitmap erano array grezzi di pixel, e ogni programma gestiva il proprio formato di archiviazione. Microsoft aveva bisogno di un modo per distribuire icone per programmi, cartelle ed elementi dell'interfaccia di sistema. I requisiti erano semplici:
- Un file per icona, indipendentemente da quante dimensioni servissero al sistema operativo
- Ricerca rapida al runtime — il sistema operativo non doveva decodificare un'immagine per conoscerne le dimensioni
- Basso consumo di memoria su sistemi con 256 KB di RAM
La soluzione fu una struttura a directory. Il file inizia con un header che indica quante immagini contiene. Segue un array di entry, ognuna delle quali descrive larghezza, altezza, profondità di colore e offset all'interno del file di una determinata immagine. Il sistema operativo legge la directory, sceglie la entry che corrisponde al contesto di visualizzazione corrente, si sposta a quell'offset e renderizza il bitmap.
Questo design precede ZIP di due anni, precede GIF di due anni e precede JPEG di sette anni. ICO era un filesystem in miniatura prima che i filesystem diventassero interessanti.
Perché ICO Esiste Affatto
Un'icona non è una singola immagine, ma un insieme di varianti della stessa grafica a risoluzioni diverse. Windows renderizza la stessa icona a 16 x 16 in un elenco file, a 32 x 32 sul desktop, a 48 x 48 nel riquadro dei dettagli di Explorer e a 256 x 256 nella visualizzazione "icone grandi". Un display ad alta DPI al 200% necessita di un'icona 32 x 32 renderizzata da una sorgente 64 x 64. Il sistema operativo sceglie la dimensione più adatta in base al contesto, e questa scelta deve essere immediata.
Se le icone fossero archiviate come singoli file PNG, il sistema operativo dovrebbe aprire più file, decodificarne ognuno e metterli in cache. Con ICO, il sistema operativo apre un file, legge una directory entry di 16 byte e salta direttamente ai dati bitmap. La directory rende il formato auto-descrittivo. Nessun decoder necessario per i metadati.
Anche per le favicon vale lo stesso principio. Quando un browser richiede /favicon.ico, riceve un file che contiene ogni dimensione di cui potrebbe aver bisogno — 16 x 16 per la scheda, 32 x 32 per la barra dei segnalibri, 180 x 180 per le scorciatoie della schermata home iOS. Il browser sceglie la giusta entry senza analizzare gli header delle immagini.
L'Header del File
L'header ICO ha due parti: l'ICONDIR e l'array ICONDIRENTRY.
ICONDIR (6 byte)
Bytes 0-1: Reserved (deve essere 0)
Bytes 2-3: Type (1 = icona, 2 = cursore)
Bytes 4-5: Count (numero di immagini, uint16 little-endian)
Ogni file ICO inizia con 00 00 01 00. I due byte successivi indicano quante immagini seguono.
ICONDIRENTRY (16 byte ciascuna)
Ogni immagine ha una propria entry:
Bytes 0: Width (in pixel; 0 significa 256)
Bytes 1: Height (in pixel; 0 significa 256)
Bytes 2: ColorCount (0 se più di 256 colori)
Bytes 3: Reserved (sempre 0)
Bytes 4-5: Planes (1 per le icone)
Bytes 6-7: BitCount (bit per pixel: 1, 4, 8, 24 o 32)
Bytes 8-11: BytesInRes (dimensione dei dati immagine in byte, uint32)
Bytes 12-15: ImageOffset (offset in byte dei dati immagine dall'inizio del file, uint32)
I campi Width e Height sono di un byte ciascuno. Il valore massimo esplicitamente archiviabile è 255. Quando un'immagine è 256 x 256 pixel, il campo memorizza 0x00, che i decoder interpretano come 256. Questa peculiarità fa parte della specifica fin dal 1985.
Ecco come appare la directory per un ICO con tre immagini — una BMP 16 x 16, una BMP 32 x 32 e una PNG 256 x 256:
Offset 0x00: 00 00 01 00 03 00 -- ICONDIR: 3 immagini
Offset 0x06: 10 10 00 00 01 00 18 00 2C 01 00 00 16 00 00 00 -- 16x16, 24bpp, BMP, 300 byte a 0x16
Offset 0x16: 20 20 00 00 01 00 18 00 A8 02 00 00 42 01 00 00 -- 32x32, 24bpp, BMP, 680 byte a 0x142
Offset 0x26: 00 00 00 00 01 00 20 00 B4 1C 00 00 EA 03 00 00 -- 256x256, 32bpp, PNG, 7348 byte a 0x3EA
Il sistema operativo legge la directory, trova la entry che soddisfa le sue esigenze e salta direttamente a ImageOffset, accedendo ai dati in memoria senza bisogno di analisi preliminari o congetture.
Perché PNG, WebP e JPEG Non Hanno Mai Sostituito ICO
Per ogni metrica moderna, PNG è un formato migliore della codifica bitmap interna di ICO. PNG ha una compressione superiore, vera trasparenza alpha e tool diffusi. WebP è ancora più compatto. JPEG gestisce le fotografie. Eppure ICO persiste. Tre motivi:
1. Risoluzioni multiple in un singolo file. PNG memorizza un'immagine sola. ICO ne memorizza un numero arbitrario. Un sito web potrebbe servire uno ZIP di PNG, ma nessun browser saprebbe come scegliere quello giusto per una favicon. La struttura a directory di ICO risolve questo problema in sei byte.
2. Vincolo delle API di sistema. Le API Windows LoadIcon, ExtractIcon e SHGetFileInfo si aspettano tutte dati ICO. L'API Win32 non ha un equivalente per container di icone PNG. Cambiare questo romperebbe ogni applicazione Windows compilata dal 1985 in poi. Microsoft non abbandona mai la retrocompatibilità.
3. Lo standard favicon. Il tag HTML <link rel="icon"> accetta PNG, SVG e ICO, ma la richiesta implicita di default per /favicon.ico precede quelle opzioni. Ogni browser da Internet Explorer 5 (1999) richiede /favicon.ico per default. I siti che non dichiarano esplicitamente un link favicon hanno comunque bisogno di un ICO a quel percorso, altrimenti il browser riceve un 404.
PNG non ha sostituito ICO perché ICO non ha mai competuto sulla qualità dell'immagine. Competeva sulla semantica del container, e nessun altro formato offre lo stesso modello di directory-in-un-file con trent'anni di supporto del sistema operativo.
Cosa C'è Dentro il Container
Ogni entry in un file ICO punta a un'immagine indipendente. I dati immagine possono essere in uno di due formati:
Codifica BMP (Legacy)
Prima di Windows Vista, tutte le immagini ICO erano bitmap BMP non compresse o compresse RLE. I dati archiviati sono un DIB (Device Independent Bitmap) — un BMP senza l'BITMAPFILEHEADER. Inizia direttamente con l'BITMAPINFOHEADER:
Bytes 0-3: Header size (40 per BITMAPINFOHEADER)
Bytes 4-7: Width (uint32)
Bytes 8-11: Height (uint32, raddoppiata per le maschere XOR + AND)
Bytes 12-13: Planes (1)
Bytes 14-15: BitCount (1, 4, 8, 24 o 32)
Bytes 16-19: Compression (0 per non compresso, 1 o 2 per RLE)
Bytes 20-23: Image size (0 se non compresso)
Bytes 24-27: X pixels per meter
Bytes 28-31: Y pixels per meter
Bytes 32-35: Colors used
Bytes 36-39: Important colors
Il campo Height nell'header DIB è il doppio dell'altezza effettiva dell'icona. Un'icona 32 x 32 memorizza 64 nel campo Height. La metà superiore è la maschera XOR (l'immagine a colori vera e propria), e la metà inferiore è la maschera AND (un bitmap di trasparenza a 1 bit). Per le icone a 32 bit con canale alpha, la maschera AND viene tipicamente ignorata.
Per le icone BMP codificate a 24 bit o inferiori senza alpha, la maschera AND è l'unico meccanismo di trasparenza. Un 1 nella maschera AND significa trasparente; uno 0 significa opaco. È così che Windows otteneva la trasparenza prima che il colore a 32 bit diventasse standard.
Codifica PNG (Windows Vista+)
A partire da Windows Vista, i file ICO possono memorizzare immagini codificate in PNG. I dati immagine all'offset specificato in ICONDIRENTRY sono un file PNG grezzo — completo della propria signature 89 50 4E 47 e della struttura completa dei chunk PNG.
Questo è il formato che si vuole per le icone 256 x 256. Una BMP a 32 bit 256 x 256 sarebbe di circa 262 KB non compressa. La stessa immagine come PNG è tipicamente di 20-60 KB. I tool per icone moderni generano entry codificate in PNG per 256 x 256 e entry codificate in BMP per le dimensioni più piccole, garantendo il miglior equilibrio tra compatibilità e dimensione del file.
Confronto tra Codifiche
| Codifica | Introduzione | Compressione | Supporto Alpha | Ideale per |
|---|---|---|---|---|
| BMP | Windows 1.0 (1985) | Nessuna o RLE | Maschera AND a 1 bit | 16 x 16 a 48 x 48 |
| PNG | Windows Vista (2006) | DEFLATE | Alpha a 8 bit | 64 x 64 a 256 x 256 |
Rilevamento e Ispezione dei File ICO
Non fidarti dell'estensione .ico. Leggi i primi sei byte e analizza la directory.
TypeScript
interface IcoEntry {
width: number
height: number
bitCount: number
bytesInRes: number
imageOffset: number
}
interface IcoInfo {
valid: boolean
type: "icon" | "cursor" | "unknown"
count: number
entries: IcoEntry[]
}
async function inspectIco(file: File): Promise<IcoInfo> {
const buffer = await file.slice(0, 1024).arrayBuffer()
const bytes = new Uint8Array(buffer)
if (bytes.length < 6)
return { valid: false, type: "unknown", count: 0, entries: [] }
const reserved = bytes[0] | (bytes[1] << 8)
const type = bytes[2] | (bytes[3] << 8)
const count = bytes[4] | (bytes[5] << 8)
if (reserved !== 0 || (type !== 1 && type !== 2)) {
return { valid: false, type: "unknown", count: 0, entries: [] }
}
const entries: IcoEntry[] = []
const dirSize = 6 + count * 16
if (bytes.length < dirSize) {
return { valid: false, type: "unknown", count: 0, entries: [] }
}
for (let i = 0; i < count; i++) {
const off = 6 + i * 16
entries.push({
width: bytes[off] === 0 ? 256 : bytes[off],
height: bytes[off + 1] === 0 ? 256 : bytes[off + 1],
bitCount: bytes[off + 6] | (bytes[off + 7] << 8),
bytesInRes:
bytes[off + 8] |
(bytes[off + 9] << 8) |
(bytes[off + 10] << 16) |
(bytes[off + 11] << 24),
imageOffset:
bytes[off + 12] |
(bytes[off + 13] << 8) |
(bytes[off + 14] << 16) |
(bytes[off + 15] << 24),
})
}
return {
valid: true,
type: type === 1 ? "icon" : "cursor",
count,
entries,
}
}
Python
import struct
from typing import TypedDict
class IcoEntry(TypedDict):
width: int
height: int
bit_count: int
bytes_in_res: int
image_offset: int
class IcoInfo(TypedDict):
valid: bool
type: str
count: int
entries: list[IcoEntry]
def inspect_ico(path: str) -> IcoInfo:
with open(path, "rb") as f:
header = f.read(1024)
if len(header) < 6:
return {"valid": False, "type": "unknown", "count": 0, "entries": []}
reserved, type_, count = struct.unpack("<HHH", header[:6])
if reserved != 0 or type_ not in (1, 2):
return {"valid": False, "type": "unknown", "count": 0, "entries": []}
dir_size = 6 + count * 16
if len(header) < dir_size:
return {"valid": False, "type": "unknown", "count": 0, "entries": []}
entries: list[IcoEntry] = []
for i in range(count):
off = 6 + i * 16
w, h, colors, _, planes, bit_count = struct.unpack("<BBBBHH", header[off:off+8])
bytes_in_res, image_offset = struct.unpack("<II", header[off+8:off+16])
entries.append({
"width": 256 if w == 0 else w,
"height": 256 if h == 0 else h,
"bit_count": bit_count,
"bytes_in_res": bytes_in_res,
"image_offset": image_offset,
})
return {
"valid": True,
"type": "icon" if type_ == 1 else "cursor",
"count": count,
"entries": entries,
}
Go
package main
import (
"encoding/binary"
"fmt"
"os"
)
type IcoEntry struct {
Width int
Height int
BitCount int
BytesInRes uint32
ImageOffset uint32
}
type IcoInfo struct {
Valid bool
Type string
Count int
Entries []IcoEntry
}
func inspectIco(path string) (IcoInfo, error) {
f, err := os.Open(path)
if err != nil {
return IcoInfo{}, err
}
defer f.Close()
buf := make([]byte, 1024)
n, _ := f.Read(buf)
buf = buf[:n]
if n < 6 {
return IcoInfo{Valid: false, Type: "unknown"}, nil
}
reserved := binary.LittleEndian.Uint16(buf[0:2])
typ := binary.LittleEndian.Uint16(buf[2:4])
count := binary.LittleEndian.Uint16(buf[4:6])
if reserved != 0 || (typ != 1 && typ != 2) {
return IcoInfo{Valid: false, Type: "unknown"}, nil
}
dirSize := 6 + int(count)*16
if n < dirSize {
return IcoInfo{Valid: false, Type: "unknown"}, nil
}
entries := make([]IcoEntry, 0, count)
for i := 0; i < int(count); i++ {
off := 6 + i*16
w := buf[off]
h := buf[off+1]
bitCount := binary.LittleEndian.Uint16(buf[off+6 : off+8])
bytesInRes := binary.LittleEndian.Uint32(buf[off+8 : off+12])
imageOffset := binary.LittleEndian.Uint32(buf[off+12 : off+16])
if w == 0 { w = 256 }
if h == 0 { h = 256 }
entries = append(entries, IcoEntry{
Width: int(w),
Height: int(h),
BitCount: int(bitCount),
BytesInRes: bytesInRes,
ImageOffset: imageOffset,
})
}
typStr := "icon"
if typ == 2 {
typStr = "cursor"
}
return IcoInfo{Valid: true, Type: typStr, Count: int(count), Entries: entries}, nil
}
PHP
function inspectIco(string $path): array {
$header = file_get_contents($path, false, null, 0, 1024);
if (strlen($header) < 6) {
return ["valid" => false, "type" => "unknown", "count" => 0, "entries" => []];
}
$reserved = unpack("v", substr($header, 0, 2))[1];
$type = unpack("v", substr($header, 2, 2))[1];
$count = unpack("v", substr($header, 4, 2))[1];
if ($reserved !== 0 || ($type !== 1 && $type !== 2)) {
return ["valid" => false, "type" => "unknown", "count" => 0, "entries" => []];
}
$dirSize = 6 + $count * 16;
if (strlen($header) < $dirSize) {
return ["valid" => false, "type" => "unknown", "count" => 0, "entries" => []];
}
$entries = [];
for ($i = 0; $i < $count; $i++) {
$off = 6 + $i * 16;
$w = ord($header[$off]);
$h = ord($header[$off + 1]);
$bitCount = unpack("v", substr($header, $off + 6, 2))[1];
$bytesInRes = unpack("V", substr($header, $off + 8, 4))[1];
$imageOffset = unpack("V", substr($header, $off + 12, 4))[1];
$entries[] = [
"width" => $w === 0 ? 256 : $w,
"height" => $h === 0 ? 256 : $h,
"bit_count" => $bitCount,
"bytes_in_res" => $bytesInRes,
"image_offset" => $imageOffset,
];
}
return [
"valid" => true,
"type" => $type === 1 ? "icon" : "cursor",
"count" => $count,
"entries" => $entries,
];
}
ImageMagick CLI
magick identify -verbose favicon.ico | grep -E "(Print size|Resolution|Colorspace|Type)"
Elenca tutte le immagini interne:
magick identify favicon.ico
Estrai una dimensione specifica:
magick favicon.ico[2] extracted-256x256.png
O semplicemente:
file favicon.ico
# favicon.ico: MS Windows icon resource - 5 icons, 16x16, 32 bits/pixel, 32x32, 32 bits/pixel
Best Practice e Casi d'Uso
ICO non è un formato immagine general-purpose. È un artefatto di deployment per contesti specifici. Usalo dove il contesto lo richiede, e usa PNG per tutto il resto.
Favicon
Per i siti web moderni, servi entrambi i formati:
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
I browser scelgono il primo formato che supportano. I browser con supporto SVG ricevono l'icona vettoriale. I browser legacy e le utility per i segnalibri tornano a ICO. Il file ICO dovrebbe contenere:
| Dimensione | Codifica | Scopo |
|---|---|---|
| 16 x 16 | BMP | Scheda del browser, IE legacy |
| 32 x 32 | BMP | Barra delle applicazioni, barra dei segnalibri |
| 48 x 48 | BMP | Scorciatoie Windows |
| 180 x 180 | PNG | Icona schermata home iOS |
| 256 x 256 | PNG | Visualizzazione icone grandi di Windows Explorer |
Un ICO favicon con queste cinque entry è tipicamente di 30-50 KB. Senza la PNG 256 x 256, scende sotto i 10 KB.
Icone per Applicazioni Windows
Gli eseguibili delle applicazioni Windows incorporano una risorsa ICO. Il sistema operativo carica la dimensione appropriata dalla directory incorporata al runtime. Per applicazioni desktop che puntano a Windows 10 e 11, includi:
- 16 x 16, 24 x 24, 32 x 32, 48 x 48 (BMP, per legacy e DPI standard)
- 64 x 64, 128 x 128, 256 x 256 (PNG, per display ad alta DPI)
Windows ridimensiona automaticamente se manca una dimensione esatta, ma il ridimensionamento appare peggiore del rendering di una dimensione nativa più piccola. Ogni dimensione che ometti costa qualità visiva.
Quando Non Usare ICO
- Immagini per contenuti web: usa WebP, JPEG o PNG. ICO non ha vantaggi di compressione e nessun beneficio di rendering nativo del browser per le immagini inline.
- Fotografie: ICO non è progettato per immagini ad alto colore e toni continui. Le dimensioni dei file esplodono.
- Asset multipiattaforma: macOS usa
.icns, non ICO. Linux usa PNG o SVG. ICO è un formato nativo Windows che funziona per caso anche sul web.
Il Futuro di ICO
Gran parte dei pronostici che davano ICO per spacciato si rivelerà sbagliata. Il motivo è lo stesso della sua origine: compatibilità all'indietro.
Le favicon SVG sono tecnicamente superiori. Si adattano a qualsiasi risoluzione, si comprimono meglio di qualsiasi formato raster e supportano animazione e interattività. Chrome, Firefox e Safari supportano tutti le favicon SVG. Eppure ICO persiste perché milioni di dispositivi, sistemi enterprise e browser legacy richiedono ancora /favicon.ico per default. Un sito senza un file ICO genera 404 nei log del server e mostra un'icona rotta in software più datato.
Windows 11 usa ancora ICO per le icone delle applicazioni, le icone delle cartelle e l'interfaccia di sistema. L'API Win32 si aspetta ancora dati ICO. Microsoft non ha mostrato alcun interesse nel sostituire questo formato — non c'è alcuna ragione di business per rompere trent'anni di compatibilità applicativa.
Quello che cambia davvero è il modo in cui vengono prodotti i file ICO. Le toolchain moderne — IconKitchen, plugin Figma, generatori online — producono file ICO con entry ad alta risoluzione codificate in PNG automaticamente. Il container rimane lo stesso; il payload migliora.
Nessuno ha mai provato affetto per il formato ICO. È un formato che funziona ovunque, non rompe nulla e non costa nulla supportare. È proprio per questo che esisterà ancora nel 2035.
Se hai bisogno di creare file ICO da immagini esistenti, JPG to ICO converte sorgenti JPG, PNG e WebP in file ICO multi-risoluzione direttamente nel browser — nessun upload, nessuna elaborazione server. Genera la matrice di dimensioni standard e sceglie automaticamente la codifica PNG per 256 x 256 e la codifica BMP per le dimensioni più piccole, offrendoti l'equilibrio ottimale tra compatibilità e dimensione del file.



