Abre un archivo ICO en un editor hexadecimal. Los primeros seis bytes:
00 00 01 00 03 00
Esa es la cabecera completa del archivo. 00 00 es el campo Reservado — siempre cero. 01 00 es el campo Tipo — 1 significa que esto es un archivo de icono (los archivos de cursor usan 2). 03 00 es el campo Contador — little-endian para 3, lo que significa que este ICO contiene tres imágenes separadas. Seis bytes para describir un contenedor que alberga tres archivos de imagen independientes. El resto del archivo es metadatos sobre esas imágenes, seguidos de los datos de la imagen en sí.
ICO no es ni un algoritmo de compresión ni un espacio de color: es un contenedor, el formato más simple y primitivo que sigue usándose a diario en todas las máquinas Windows y en los principales navegadores.
De dónde viene ICO
Microsoft introdujo ICO con Windows 1.0 en 1985. El ecosistema de PC en 1985 no tenía concepto de archivo de imagen estandarizado. No existía JPEG, no existía PNG, no existía GIF. Los datos de mapa de bits eran matrices raw de píxeles, y cada programa manejaba su propio almacenamiento. Microsoft necesitaba una forma de distribuir iconos para programas, carpetas y elementos de la interfaz del sistema. Los requisitos eran simples:
- Un archivo por icono, sin importar cuántos tamaños necesitara el sistema operativo
- Búsqueda rápida en tiempo de ejecución — el sistema operativo no debería tener que decodificar una imagen para conocer sus dimensiones
- Huella de memoria pequeña en sistemas con 256 KB de RAM
La solución fue una estructura de directorio. El archivo comienza con una cabecera que indica cuántas imágenes contiene. Luego viene una matriz de entradas, cada una describiendo el ancho, alto, profundidad de bits y desplazamiento dentro del archivo de una imagen. El sistema operativo lee el directorio, elige la entrada que coincide con el contexto de pantalla actual, se desplaza a esa posición y renderiza el mapa de bits.
Este diseño es anterior a ZIP por dos años, anterior a GIF por dos años, y anterior a JPEG por siete años. ICO era un sistema de archivos en miniatura antes de que los sistemas de archivos se volvieran interesantes.
Por qué existe ICO
Un icono no es una sola imagen, sino un conjunto de imágenes en distintas resoluciones. Windows renderiza el mismo icono a 16 x 16 en una lista de archivos, a 32 x 32 en el escritorio, a 48 x 48 en el panel de detalles del Explorador, y a 256 x 256 en la vista "extra grande". Una pantalla de alta DPI al 200% de escala necesita un icono de 32 x 32 renderizado desde una fuente de 64 x 64. El sistema operativo debe decidir qué tamaño usar según el contexto, y esa elección tiene que ser prácticamente instantánea.
Si los iconos se almacenaran como archivos PNG individuales, el sistema operativo necesitaría abrir múltiples archivos, decodificar cada uno y almacenar en caché los resultados. Con ICO, el sistema operativo abre un archivo, lee una entrada de directorio de 16 bytes y salta directamente a los datos del mapa de bits. El directorio hace que el formato sea autodescriptivo. No se necesita decodificador para los metadatos.
En el caso de los favicones sucede exactamente lo mismo. Cuando un navegador solicita /favicon.ico, obtiene un archivo que contiene cada tamaño que podría necesitar — 16 x 16 para la pestaña, 32 x 32 para la barra de marcadores, 180 x 180 para los accesos directos de la pantalla de inicio de iOS. El navegador elige la entrada correcta sin analizar cabeceras de imagen.
La cabecera del archivo
La cabecera ICO tiene dos partes: el ICONDIR y la matriz ICONDIRENTRY.
ICONDIR (6 bytes)
Bytes 0-1: Reservado (debe ser 0)
Bytes 2-3: Tipo (1 = icono, 2 = cursor)
Bytes 4-5: Contador (número de imágenes, uint16 little-endian)
Cada archivo ICO comienza con 00 00 01 00. Los siguientes dos bytes te dicen cuántas imágenes siguen.
ICONDIRENTRY (16 bytes cada una)
Cada imagen recibe una entrada:
Bytes 0: Ancho (en píxeles; 0 significa 256)
Bytes 1: Alto (en píxeles; 0 significa 256)
Bytes 2: ColorCount (0 si más de 256 colores)
Bytes 3: Reservado (siempre 0)
Bytes 4-5: Planes (1 para iconos)
Bytes 6-7: BitCount (bits por píxel: 1, 4, 8, 24 o 32)
Bytes 8-11: BytesInRes (tamaño de los datos de imagen en bytes, uint32)
Bytes 12-15: ImageOffset (desplazamiento en bytes a los datos de imagen desde el inicio del archivo, uint32)
Los campos de Ancho y Alto ocupan un byte cada uno. El valor máximo explícitamente almacenable es 255. Cuando una imagen es de 256 x 256 píxeles, el campo almacena 0x00, que los decodificadores interpretan como 256. Esta peculiaridad ha sido parte de la especificación desde 1985.
Así es como se ve el directorio para un ICO con tres imágenes — un BMP de 16 x 16, un BMP de 32 x 32 y un PNG de 256 x 256:
Offset 0x00: 00 00 01 00 03 00 -- ICONDIR: 3 imágenes
Offset 0x06: 10 10 00 00 01 00 18 00 2C 01 00 00 16 00 00 00 -- 16x16, 24bpp, BMP, 300 bytes en 0x16
Offset 0x16: 20 20 00 00 01 00 18 00 A8 02 00 00 42 01 00 00 -- 32x32, 24bpp, BMP, 680 bytes en 0x142
Offset 0x26: 00 00 00 00 01 00 20 00 B4 1C 00 00 EA 03 00 00 -- 256x256, 32bpp, PNG, 7348 bytes en 0x3EA
El sistema operativo lee el directorio, localiza la entrada que necesita y salta directamente a ImageOffset, accediendo a la memoria sin tener que analizar datos ni hacer conjeturas.
Por qué PNG, WebP y JPEG nunca reemplazaron a ICO
Por cada métrica moderna, PNG es un formato mejor que la codificación de mapa de bits interna de ICO. PNG tiene mejor compresión, transparencia alpha real y herramientas generalizadas. WebP es aún más pequeño. JPEG maneja fotografías. Sin embargo, ICO persiste. Tres razones:
1. Múltiples resoluciones en un solo archivo. PNG almacena una imagen. ICO almacena un número arbitrario. Un sitio web podría servir un ZIP de PNGs, pero ningún navegador sabría cómo elegir el correcto para un favicon. La estructura de directorio de ICO resuelve esto en seis bytes.
2. Vinculación con la API del sistema. LoadIcon, ExtractIcon y SHGetFileInfo de Windows esperan datos ICO. La API Win32 no tiene equivalente para contenedores de iconos PNG. Cambiar esto rompería todas las aplicaciones Windows compiladas desde 1985, y Microsoft nunca sacrifica la compatibilidad con versiones anteriores.
3. El estándar de favicon. La etiqueta HTML <link rel="icon"> acepta PNG, SVG e ICO, pero la solicitud implícita por defecto de /favicon.ico es anterior a esas opciones. Cada navegador desde Internet Explorer 5 (1999) solicita /favicon.ico por defecto. Los sitios que no declaran explícitamente un enlace de favicon aún necesitan un ICO en esa ruta o el navegador obtiene un 404.
PNG no reemplazó a ICO porque ICO nunca compitió en calidad de imagen. Competía en semántica de contenedor, y ningún otro formato ofrece el mismo modelo de directorio-en-un-archivo con treinta años de soporte del sistema operativo.
Qué hay dentro del contenedor
Cada entrada en un archivo ICO apunta a una imagen independiente. Los datos de la imagen pueden tener uno de dos formatos:
Codificación BMP (Legado)
Antes de Windows Vista, todas las imágenes ICO eran mapas de bits BMP sin comprimir o comprimidos con RLE. Los datos almacenados son un DIB (Device Independent Bitmap) — un BMP sin el BITMAPFILEHEADER. Comienza directamente con el BITMAPINFOHEADER:
Bytes 0-3: Tamaño de cabecera (40 para BITMAPINFOHEADER)
Bytes 4-7: Ancho (uint32)
Bytes 8-11: Alto (uint32, duplicado para máscaras XOR + AND)
Bytes 12-13: Planes (1)
Bytes 14-15: BitCount (1, 4, 8, 24 o 32)
Bytes 16-19: Compresión (0 para sin comprimir, 1 o 2 para RLE)
Bytes 20-23: Tamaño de imagen (0 si sin comprimir)
Bytes 24-27: X píxeles por metro
Bytes 28-31: Y píxeles por metro
Bytes 32-35: Colores usados
Bytes 36-39: Colores importantes
El campo Alto en la cabecera DIB es el doble del alto real del icono. Un icono de 32 x 32 almacena 64 en el campo Alto. La mitad superior es la máscara XOR (la imagen de color real), y la mitad inferior es la máscara AND (un mapa de bits de transparencia de 1 bit). Para iconos de 32 bits con canal alpha, la máscara AND normalmente se ignora.
Para iconos codificados en BMP de 24 bits o menos sin alpha, la máscara AND es el único mecanismo de transparencia. Un 1 en la máscara AND significa transparente; un 0 significa opaco. Así es como Windows logró la transparencia antes de que el color de 32 bits se volviera estándar.
Codificación PNG (Windows Vista+)
A partir de Windows Vista, los archivos ICO pueden almacenar imágenes codificadas en PNG. Los datos de la imagen en el desplazamiento especificado en ICONDIRENTRY son un archivo PNG raw — completo con su propia firma 89 50 4E 47 y estructura completa de chunks PNG.
Este es el formato que quieres para iconos de 256 x 256. Un BMP de 32 bits de 256 x 256 sería aproximadamente 262 KB sin comprimir. La misma imagen como PNG suele ser de 20-60 KB. Las herramientas de iconos modernas generan entradas codificadas en PNG para 256 x 256 y entradas codificadas en BMP para tamaños más pequeños, dando el mejor equilibrio entre compatibilidad y tamaño de archivo.
Comparación de codificaciones
| Codificación | Introducida | Compresión | Soporte Alpha | Mejor para |
|---|---|---|---|---|
| BMP | Windows 1.0 (1985) | Ninguna o 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 |
Detección e inspección de archivos ICO
No confíes en la extensión .ico. Lee los primeros seis bytes y analiza el directorio.
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)"
Listar todas las imágenes internas:
magick identify favicon.ico
Extraer un tamaño específico:
magick favicon.ico[2] extracted-256x256.png
O simplemente:
file favicon.ico
# favicon.ico: MS Windows icon resource - 5 icons, 16x16, 32 bits/pixel, 32x32, 32 bits/pixel
Buenas prácticas y casos de uso
ICO no es un formato de imagen de propósito general. Es un artefacto de despliegue para contextos específicos. Úsalo donde el contexto lo exija, y usa PNG en todos los demás casos.
Favicons
Para sitios web modernos, sirve ambos formatos:
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
Los navegadores eligen el primer formato que soportan. Los navegadores compatibles con SVG obtienen el icono vectorial. Los navegadores heredados y las utilidades de marcadores recurren al ICO. El archivo ICO debería contener:
| Tamaño | Codificación | Propósito |
|---|---|---|
| 16 x 16 | BMP | Pestaña del navegador, IE heredado |
| 32 x 32 | BMP | Barra de tareas, barra de marcadores |
| 48 x 48 | BMP | Accesos directos de Windows |
| 180 x 180 | PNG | Icono de pantalla de inicio de iOS |
| 256 x 256 | PNG | Vista extra grande del Explorador de Windows |
Un favicon ICO con estas cinco entradas suele ser de 30-50 KB. Sin el PNG de 256 x 256, baja a menos de 10 KB.
Iconos de aplicaciones Windows
Los ejecutables de aplicaciones Windows incrustan un recurso ICO. El sistema operativo carga el tamaño apropiado desde el directorio incrustado en tiempo de ejecución. Para aplicaciones de escritorio dirigidas a Windows 10 y 11, incluye:
- 16 x 16, 24 x 24, 32 x 32, 48 x 48 (BMP, para heredado y DPI estándar)
- 64 x 64, 128 x 128, 256 x 256 (PNG, para pantallas de alta DPI)
Windows escala automáticamente hacia abajo si falta un tamaño exacto, pero el escalado se ve peor que renderizar un tamaño nativo más pequeño. Cada tamaño que omites cuesta calidad visual.
Cuándo no usar ICO
- Imágenes de contenido web: Usa WebP, JPEG o PNG. ICO no tiene ventaja de compresión ni beneficio de renderizado nativo del navegador para imágenes en línea.
- Fotografías: ICO no está diseñado para imágenes de tono continuo de alto color. Los tamaños de archivo se disparan.
- Recursos multiplataforma: macOS usa
.icns, no ICO. Linux usa PNG o SVG. ICO es un formato nativo de Windows que funciona en la web por casualidad.
El futuro de ICO
La mayoría de profecías que anuncian la muerte de ICO se quedarán cortas. La razón es la misma que su origen: compatibilidad hacia atrás.
Los favicones SVG son técnicamente superiores. Escalan a cualquier resolución, comprimen mejor que cualquier formato raster y soportan animación e interactividad. Chrome, Firefox y Safari soportan favicones SVG. Sin embargo, ICO persiste porque millones de dispositivos, sistemas empresariales y navegadores heredados aún solicitan /favicon.ico por defecto. Un sitio sin un archivo ICO genera 404s en los logs del servidor y muestra un icono roto en software más antiguo.
Windows 11 todavía usa ICO para iconos de aplicaciones, iconos de carpetas e interfaz de usuario del sistema. La API Win32 todavía espera datos ICO. Microsoft no ha mostrado interés en reemplazar este formato — no hay razón de negocio para romper treinta años de compatibilidad de aplicaciones.
Lo que realmente ha evolucionado es la forma en que se generan los archivos ICO. Las cadenas de herramientas modernas — IconKitchen, plugins de Figma, generadores online — producen archivos ICO con entradas de alta resolución codificadas en PNG automáticamente. El contenedor sigue siendo el mismo; lo que mejora es el contenido.
A nadie le entusiasma especialmente el formato ICO. Pero funciona en cualquier parte, no rompe nada y su mantenimiento no supone ningún coste. Por eso mismo seguirá vigente en 2035.
Si necesitas crear archivos ICO a partir de imágenes existentes, JPG to ICO convierte fuentes JPG, PNG y WebP en archivos ICO de múltiples resoluciones directamente en tu navegador — sin subidas, sin procesamiento en servidor. Genera la matriz de tamaños estándar y elige automáticamente codificación PNG para 256 x 256 y codificación BMP para tamaños más pequeños, dándote el equilibrio óptimo entre compatibilidad y tamaño de archivo.



