Abra um arquivo ICO em um editor hexadecimal. Os primeiros seis bytes:
00 00 01 00 03 00
Esse é o header completo do arquivo. 00 00 é o campo Reserved — sempre zero. 01 00 é o campo Type — 1 significa que este é um arquivo de ícone (arquivos de cursor usam 2). 03 00 é o campo Count — little-endian para 3, o que significa que este ICO contém três imagens separadas. Seis bytes para descrever um container que armazena três arquivos de imagem independentes. O restante do arquivo é metadados sobre essas imagens, seguido pelos dados da imagem em si.
ICO não é nem algoritmo de compressão, nem espaço de cor: é um container — o formato de container mais simples e primitivo ainda em uso diário em todas as máquinas Windows e em todos os principais navegadores.
De Onde Veio o ICO
A Microsoft introduziu o ICO com o Windows 1.0 em 1985. O ecossistema de PCs em 1985 não tinha conceito de arquivo de imagem padronizado. Não existia JPEG, PNG ou GIF. Os dados de bitmap eram arrays brutos de pixels, e cada programa lidava com seu próprio armazenamento. A Microsoft precisava de uma forma de entregar ícones para programas, pastas e elementos da interface do sistema. Os requisitos eram simples:
- Um arquivo por ícone, independentemente de quantos tamanhos o SO precisasse
- Busca rápida em tempo de execução — o SO não deveria precisar decodificar uma imagem para saber suas dimensões
- Pequena pegada de memória em sistemas com 256 KB de RAM
A solução foi uma estrutura de diretório. O arquivo começa com um header que diz quantas imagens estão dentro. Depois vem um array de entradas, cada uma descrevendo a largura, altura, profundidade de bits e offset de uma imagem dentro do arquivo. O SO lê o diretório, escolhe a entrada que corresponde ao contexto de exibição atual, busca aquele offset e renderiza o bitmap.
Este design é anterior ao ZIP em dois anos, anterior ao GIF em dois anos e anterior ao JPEG em sete anos. O ICO era um sistema de arquivos em miniatura antes que sistemas de arquivos se tornassem interessantes.
Por que o ICO Existe
Um ícone não é uma única imagem, mas sim um conjunto de imagens em diferentes tamanhos. O Windows renderiza o mesmo ícone em 16 x 16 na lista de arquivos, em 32 x 32 na área de trabalho, em 48 x 48 no painel de detalhes do Explorer e em 256 x 256 na visualização "extra large". Uma tela de alta DPI em escala de 200% precisa de um ícone de 32 x 32 renderizado a partir de uma fonte de 64 x 64. O SO decide qual tamanho usar com base no contexto, e essa escolha tem de ser feita num instante.
Se os ícones fossem armazenados como arquivos PNG individuais, o SO precisaria abrir vários arquivos, decodificar cada um e armazenar os resultados em cache. Com o ICO, o SO abre um arquivo, lê uma entrada de diretório de 16 bytes e pula diretamente para os dados do bitmap. O diretório torna o formato autodescritivo. Nenhum decoder é necessário para metadados.
O mesmo vale para os favicons. Quando um navegador solicita /favicon.ico, recebe um arquivo que contém todos os tamanhos de que pode precisar — 16 x 16 para a aba, 32 x 32 para a barra de favoritos, 180 x 180 para atalhos da tela inicial do iOS. O navegador escolhe a entrada certa sem analisar headers de imagem.
O Header do Arquivo
O header do ICO tem duas partes: o ICONDIR e o array ICONDIRENTRY.
ICONDIR (6 bytes)
Bytes 0-1: Reserved (must be 0)
Bytes 2-3: Type (1 = icon, 2 = cursor)
Bytes 4-5: Count (number of images, little-endian uint16)
Todo arquivo ICO começa com 00 00 01 00. Os dois bytes seguintes dizem quantas imagens vêm depois.
ICONDIRENTRY (16 bytes cada)
Cada imagem recebe uma entrada:
Bytes 0: Width (in pixels; 0 means 256)
Bytes 1: Height (in pixels; 0 means 256)
Bytes 2: ColorCount (0 if more than 256 colors)
Bytes 3: Reserved (always 0)
Bytes 4-5: Planes (1 for icons)
Bytes 6-7: BitCount (bits per pixel: 1, 4, 8, 24, or 32)
Bytes 8-11: BytesInRes (size of image data in bytes, uint32)
Bytes 12-15: ImageOffset (byte offset to image data from file start, uint32)
Os campos Width e Height têm um byte cada. O valor máximo explicitamente armazenável é 255. Quando uma imagem tem 256 x 256 pixels, o campo armazena 0x00, que os decoders interpretam como 256. Essa peculiaridade faz parte da especificação desde 1985.
Veja como fica o diretório de um ICO com três imagens — um BMP de 16 x 16, um BMP de 32 x 32 e um PNG de 256 x 256:
Offset 0x00: 00 00 01 00 03 00 -- ICONDIR: 3 images
Offset 0x06: 10 10 00 00 01 00 18 00 2C 01 00 00 16 00 00 00 -- 16x16, 24bpp, BMP, 300 bytes at 0x16
Offset 0x16: 20 20 00 00 01 00 18 00 A8 02 00 00 42 01 00 00 -- 32x32, 24bpp, BMP, 680 bytes at 0x142
Offset 0x26: 00 00 00 00 01 00 20 00 B4 1C 00 00 EA 03 00 00 -- 256x256, 32bpp, PNG, 7348 bytes at 0x3EA
O SO lê o diretório, encontra a entrada que corresponde às suas necessidades e busca o ImageOffset — acesso direto à memória, sem parsing ou suposições.
Por que PNG, WebP e JPEG Nunca Substituíram o ICO
Por todas as métricas modernas, PNG é um formato melhor que a codificação interna de bitmap do ICO. PNG tem melhor compressão, transparência alpha verdadeira e ferramentas amplamente disponíveis. WebP é ainda menor. JPEG lida com fotografias. Mesmo assim, o ICO persiste. Três razões:
1. Arquivo único com múltiplas resoluções. PNG armazena uma imagem. ICO armazena um número arbitrário. Um site poderia servir um ZIP de PNGs, mas nenhum navegador saberia como escolher o certo para um favicon. A estrutura de diretório do ICO resolve isso em seis bytes.
2. Vínculo com API do sistema. As funções LoadIcon, ExtractIcon e SHGetFileInfo do Windows esperam dados ICO. A API Win32 não tem equivalente para containers de ícones PNG. Mudar isso quebraria todos os aplicativos Windows compilados desde 1985. A Microsoft nunca abre mão da compatibilidade retroativa.
3. O padrão favicon. A tag HTML <link rel="icon"> aceita PNG, SVG e ICO, mas a solicitação implícita padrão por /favicon.ico é anterior a essas opções. Todo navegador desde o Internet Explorer 5 (1999) solicita /favicon.ico por padrão. Sites que não declaram explicitamente um link de favicon ainda precisam de um ICO nesse caminho, ou o navegador recebe um 404.
PNG não substituiu o ICO porque o ICO nunca competiu em qualidade de imagem. Ele competia em semântica de container, e nenhum outro formato oferece o mesmo modelo de diretório-em-arquivo com trinta anos de suporte do sistema operacional.
O que Há Dentro do Container
Cada entrada em um arquivo ICO aponta para uma imagem independente. Os dados da imagem podem estar em um de dois formatos:
Codificação BMP (Legado)
Antes do Windows Vista, todas as imagens ICO eram bitmaps BMP sem compressão ou comprimidos com RLE. Os dados armazenados são um DIB (Device Independent Bitmap) — um BMP sem o BITMAPFILEHEADER. Começa diretamente com o BITMAPINFOHEADER:
Bytes 0-3: Header size (40 for BITMAPINFOHEADER)
Bytes 4-7: Width (uint32)
Bytes 8-11: Height (uint32, doubled for XOR + AND masks)
Bytes 12-13: Planes (1)
Bytes 14-15: BitCount (1, 4, 8, 24, or 32)
Bytes 16-19: Compression (0 for uncompressed, 1 or 2 for RLE)
Bytes 20-23: Image size (0 if uncompressed)
Bytes 24-27: X pixels per meter
Bytes 28-31: Y pixels per meter
Bytes 32-35: Colors used
Bytes 36-39: Important colors
O campo Height no header DIB é o dobro da altura real do ícone. Um ícone de 32 x 32 armazena 64 no campo Height. A metade superior é a máscara XOR (a imagem colorida real), e a metade inferior é a máscara AND (um bitmap de transparência de 1 bit). Para ícones de 32 bits com canal alpha, a máscara AND é tipicamente ignorada.
Para ícones codificados em BMP de 24 bits ou menos sem alpha, a máscara AND é o único mecanismo de transparência. Um 1 na máscara AND significa transparente; um 0 significa opaco. Foi assim que o Windows conseguiu transparência antes que a cor de 32 bits se tornasse padrão.
Codificação PNG (Windows Vista+)
A partir do Windows Vista, arquivos ICO podem armazenar imagens codificadas em PNG. Os dados da imagem no offset especificado em ICONDIRENTRY são um arquivo PNG raw — completo com sua própria assinatura 89 50 4E 47 e estrutura completa de chunks PNG.
Este é o formato que você quer para ícones de 256 x 256. Um BMP de 256 x 256 em 32 bits teria aproximadamente 262 KB sem compressão. A mesma imagem como PNG costuma ter de 20 a 60 KB. Ferramentas modernas de ícones geram entradas codificadas em PNG para 256 x 256 e entradas codificadas em BMP para tamanhos menores, oferecendo o melhor equilíbrio entre compatibilidade e tamanho de arquivo.
Comparação de Codificações
| Codificação | Introduzido | Compressão | Suporte Alpha | Ideal Para |
|---|---|---|---|---|
| BMP | Windows 1.0 (1985) | Nenhuma ou RLE | Máscara AND de 1 bit | 16 x 16 a 48 x 48 |
| PNG | Windows Vista (2006) | DEFLATE | Alpha de 8 bits | 64 x 64 a 256 x 256 |
Detectando e Inspecionando Arquivos ICO
Não confie na extensão .ico. Leia os primeiros seis bytes e analise o diretório.
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,
];
}
CLI do ImageMagick
magick identify -verbose favicon.ico | grep -E "(Print size|Resolution|Colorspace|Type)"
Lista todas as imagens internas:
magick identify favicon.ico
Extrai um tamanho específico:
magick favicon.ico[2] extracted-256x256.png
Ou simplesmente:
file favicon.ico
# favicon.ico: MS Windows icon resource - 5 icons, 16x16, 32 bits/pixel, 32x32, 32 bits/pixel
Boas Práticas e Casos de Uso
ICO não é um formato de imagem de uso geral. É um artefato de implantação para contextos específicos. Use-o onde o contexto exigir, e use PNG em todos os outros lugares.
Favicons
Para sites modernos, sirva ambos os formatos:
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
Os navegadores escolhem o primeiro formato que suportam. Navegadores com suporte a SVG recebem o ícone vetorial. Navegadores legados e utilitários de favoritos recorrem ao ICO. O arquivo ICO deve conter:
| Tamanho | Codificação | Finalidade |
|---|---|---|
| 16 x 16 | BMP | Aba do navegador, IE legado |
| 32 x 32 | BMP | Barra de tarefas, barra de favoritos |
| 48 x 48 | BMP | Atalhos do Windows |
| 180 x 180 | PNG | Ícone da tela inicial do iOS |
| 256 x 256 | PNG | Visualização extra-large do Windows Explorer |
Um favicon ICO com essas cinco entradas costuma ter de 30 a 50 KB. Sem o PNG de 256 x 256, cai para menos de 10 KB.
Ícones de Aplicativos Windows
Os executáveis de aplicativos Windows incorporam um recurso ICO. O SO carrega o tamanho apropriado do diretório incorporado em tempo de execução. Para aplicativos de desktop destinados ao Windows 10 e 11, inclua:
- 16 x 16, 24 x 24, 32 x 32, 48 x 48 (BMP, para legado e DPI padrão)
- 64 x 64, 128 x 128, 256 x 256 (PNG, para telas de alta DPI)
O Windows escala automaticamente para baixo se um tamanho exato estiver ausente, mas o scaling fica pior do que renderizar um tamanho nativo menor. Cada tamanho que você omite custa qualidade visual.
Quando Não Usar ICO
- Imagens de conteúdo web: Use WebP, JPEG ou PNG. ICO não tem vantagem de compressão nem benefício de renderização nativa do navegador para imagens inline.
- Fotografias: ICO não foi projetado para imagens contínuas de alta cor. Os tamanhos de arquivo explodem.
- Recursos multiplataforma: macOS usa
.icns, não ICO. Linux usa PNG ou SVG. ICO é um formato nativo do Windows que por acaso funciona na web.
O Futuro do ICO
O ICO resistirá à maioria dos prognósticos que anunciam sua extinção. O motivo é o mesmo de sua origem: compatibilidade retroativa.
Favicons SVG são tecnicamente superiores. Eles escalam para qualquer resolução, comprimem melhor do que qualquer formato raster e suportam animação e interatividade. Chrome, Firefox e Safari suportam favicons SVG. Mesmo assim, o ICO persiste porque milhões de dispositivos, sistemas corporativos e navegadores legados ainda solicitam /favicon.ico por padrão. Um site sem um arquivo ICO gera 404s nos logs do servidor e mostra um ícone quebrado em softwares mais antigos.
O Windows 11 ainda usa ICO para ícones de aplicativos, ícones de pastas e interface do sistema. A API Win32 ainda espera dados ICO. A Microsoft não demonstrou interesse em substituir este formato — não há razão de negócio para quebrar trinta anos de compatibilidade de aplicativos.
A verdadeira mudança está na forma como os arquivos ICO são produzidos. Toolchains modernas — IconKitchen, plugins do Figma, geradores online — geram arquivos ICO com entradas de alta resolução codificadas em PNG automaticamente. O container permanece o mesmo; o payload fica melhor.
O ICO não é um formato que desperta paixão. É um formato que funciona em toda parte, não quebra nada e não custa nada para manter. É exatamente por isso que ele ainda estará por aí em 2035.
Se você precisar criar arquivos ICO a partir de imagens existentes, JPG to ICO converte fontes JPG, PNG e WebP em arquivos ICO multi-resolução diretamente no seu navegador — sem uploads, sem processamento no servidor. Ele gera a matriz de tamanhos padrão e escolhe automaticamente a codificação PNG para 256 x 256 e a codificação BMP para tamanhos menores, oferecendo o equilíbrio ideal entre compatibilidade e tamanho de arquivo.



