El favicon es la pequeña imagen que aparece en la pestaña del navegador, marcadores y resultados de búsqueda. El formato ICO, aunque antiguo, sigue siendo esencial porque es el único que puede contener múltiples tamaños en un solo archivo.
## ICO vs PNG vs SVG como favicon: comparativa
| Formato | Tamaños múltiples | Transparencia | Vectorial | Soporte universal |
|---|---|---|---|---|
| **ICO** | **Sí (en un archivo)** | Sí | No | **100%** (IE incluido) |
| PNG | No (1 por archivo) | Sí | No | 95% |
| SVG | N/A (vectorial) | Sí | **Sí** | 80% (no IE) |
**Estrategia moderna:** ICO con 16×16 y 32×32 para compatibilidad + PNG 192×192 y 512×512 para PWA + SVG para navegadores modernos.
## Crear favicon ICO multi-tamaño con Pillow
```python
from PIL import Image
def crear_favicon_ico(imagen_fuente, ruta_ico, tamaños=None):
"""
Crea un ICO multi-tamaño desde cualquier imagen fuente.
La imagen fuente debería ser al menos 512×512 píxeles.
"""
if tamaños is None:
tamaños = [16, 32, 48, 64, 128, 256]
img = Image.open(imagen_fuente)
# Convertir a RGBA para preservar transparencia
if img.mode != 'RGBA':
img = img.convert('RGBA')
# Crear versiones a cada tamaño con filtro de alta calidad
versiones = []
for tam in tamaños:
version = img.resize((tam, tam), Image.LANCZOS)
versiones.append(version)
# Guardar ICO (Pillow soporta ICO multi-tamaño nativamente)
versiones[0].save(
ruta_ico,
format='ICO',
sizes=[(t, t) for t in tamaños],
append_images=versiones[1:],
)
print(f"ICO creado: {ruta_ico} con tamaños {tamaños}")
# Uso básico
crear_favicon_ico('logo_512x512.png', 'favicon.ico')
# Solo los tamaños más comunes
crear_favicon_ico('logo.png', 'favicon_simple.ico', tamaños=[16, 32, 48])
```
## Pipeline completo de favicons para un sitio web
```python
from PIL import Image
from pathlib import Path
def generar_todos_los_favicons(imagen_fuente, directorio_salida='.'):
"""
Genera el conjunto completo de favicons para un sitio web moderno:
- favicon.ico (multi-tamaño: 16, 32, 48)
- favicon-16x16.png
- favicon-32x32.png
- apple-touch-icon.png (180x180)
- android-chrome-192x192.png
- android-chrome-512x512.png
- og-image.png (1200x630 para Open Graph)
"""
salida = Path(directorio_salida)
salida.mkdir(exist_ok=True)
img = Image.open(imagen_fuente)
if img.mode != 'RGBA':
img = img.convert('RGBA')
# Favicon ICO
ico_sizes = [16, 32, 48]
ico_imgs = [img.resize((s, s), Image.LANCZOS) for s in ico_sizes]
ico_imgs[0].save(
salida / 'favicon.ico', format='ICO',
sizes=[(s, s) for s in ico_sizes],
append_images=ico_imgs[1:],
)
# PNG favicons
png_configs = [
('favicon-16x16.png', 16),
('favicon-32x32.png', 32),
('apple-touch-icon.png', 180),
('android-chrome-192x192.png', 192),
('android-chrome-512x512.png', 512),
]
for nombre, tam in png_configs:
img.resize((tam, tam), Image.LANCZOS).save(salida / nombre, format='PNG')
print(f" Creado: {nombre} ({tam}x{tam})")
# Open Graph image (fondo blanco porque OG no usa transparencia)
og_size = (1200, 630)
og_img = Image.new('RGB', og_size, (255, 255, 255))
icon_size = min(og_size[1] - 100, 530)
icon_resized = img.resize((icon_size, icon_size), Image.LANCZOS)
x = (og_size[0] - icon_size) // 2
y = (og_size[1] - icon_size) // 2
og_img.paste(icon_resized, (x, y), icon_resized)
og_img.save(salida / 'og-image.png', format='PNG')
print(f" Creado: og-image.png (1200x630)")
print(f"\nFavicons generados en: {salida.resolve()}")
generar_todos_los_favicons('logo.png', 'public/')
```
## Web Manifest (PWA icons)
```python
import json
from pathlib import Path
def generar_web_manifest(nombre_app, color_fondo='#ffffff', color_tema='#000000'):
"""Genera site.webmanifest para PWA."""
manifest = {
"name": nombre_app,
"short_name": nombre_app[:12],
"icons": [
{"src": "/android-chrome-192x192.png", "sizes": "192x192", "type": "image/png"},
{"src": "/android-chrome-512x512.png", "sizes": "512x512", "type": "image/png"},
{"src": "/android-chrome-512x512.png", "sizes": "512x512", "type": "image/png",
"purpose": "maskable"},
],
"theme_color": color_tema,
"background_color": color_fondo,
"display": "standalone",
"start_url": "/",
}
with open('site.webmanifest', 'w') as f:
json.dump(manifest, f, indent=2, ensure_ascii=False)
print("site.webmanifest generado")
generar_web_manifest("Mi App", color_tema="#1a73e8")
```
## HTML: configuración correcta de favicons
```html
```
## Crear ICO desde SVG (sin rasterización manual)
```python
import subprocess
import tempfile
import os
from PIL import Image
def svg_a_ico(svg_path, ico_path, tamaños=[16, 32, 48, 64]):
"""Convierte SVG a ICO usando Inkscape o rsvg-convert + Pillow."""
try:
# Intentar con Inkscape
with tempfile.NamedTemporaryFile(suffix='.png', delete=False) as tmp:
tmp_path = tmp.name
subprocess.run([
'inkscape', '--export-type=png',
f'--export-width=512', f'--export-height=512',
f'--export-filename={tmp_path}', svg_path
], check=True, capture_output=True)
crear_favicon_ico(tmp_path, ico_path, tamaños=tamaños)
finally:
if os.path.exists(tmp_path):
os.unlink(tmp_path)
def crear_favicon_ico(imagen_fuente, ruta_ico, tamaños=None):
if tamaños is None:
tamaños = [16, 32, 48]
img = Image.open(imagen_fuente).convert('RGBA')
versiones = [img.resize((t, t), Image.LANCZOS) for t in tamaños]
versiones[0].save(ruta_ico, format='ICO',
sizes=[(t, t) for t in tamaños],
append_images=versiones[1:])
print(f"ICO: {ruta_ico}")
```
## Verificar el favicon en producción
```bash
# Ver headers del favicon
curl -I https://tudominio.com/favicon.ico
# Descargar y verificar tamaños
curl -sO https://tudominio.com/favicon.ico
python3 -c "
from PIL import Image
img = Image.open('favicon.ico')
print('Tamaños en el ICO:', img.info.get('sizes', [(img.width, img.height)]))
"
# Herramientas online: realfavicongenerator.net, favicon.io
```
## Checklist de favicons para producción
- [ ] `favicon.ico` — 16×16, 32×32 (mínimo)
- [ ] `favicon-16x16.png` y `favicon-32x32.png`
- [ ] `apple-touch-icon.png` — 180×180
- [ ] `android-chrome-192x192.png` y `android-chrome-512x512.png`
- [ ] `site.webmanifest` con referencias a los PNG
- [ ] `og-image.png` — 1200×630 para redes sociales
- [ ] `favicon.svg` (opcional, para navegadores muy modernos)
Guía