# Reducir el tamaño de imágenes con Python y Pillow
Optimizar imágenes es fundamental para el rendimiento web y el almacenamiento eficiente. Python con Pillow ofrece control total sobre calidad, dimensiones y formato.
## Instalación
```bash
pip install Pillow
```
## Redimensionar manteniendo proporción
```python
from PIL import Image
def redimensionar(ruta_in, ancho_max, ruta_out):
with Image.open(ruta_in) as img:
ratio = ancho_max / img.width
nuevo_alto = int(img.height * ratio)
img_redim = img.resize((ancho_max, nuevo_alto), Image.LANCZOS)
img_redim.save(ruta_out)
print(f"{img.width}x{img.height} -> {ancho_max}x{nuevo_alto}")
redimensionar("foto.jpg", 800, "foto_800px.jpg")
```
## Comprimir JPG
```python
from PIL import Image
import os
def comprimir_jpg(entrada, salida, calidad=80):
with Image.open(entrada) as img:
if img.mode in ('RGBA', 'P', 'LA'):
img = img.convert('RGB')
img.save(salida, format='JPEG', quality=calidad, optimize=True, progressive=True)
kb_in = os.path.getsize(entrada) // 1024
kb_out = os.path.getsize(salida) // 1024
reduccion = 100 - kb_out * 100 // kb_in
print(f"{kb_in} KB -> {kb_out} KB ({reduccion}% menos)")
comprimir_jpg("original.jpg", "comprimida.jpg", calidad=80)
```
## Convertir a WebP
```python
from PIL import Image
import os
def a_webp(entrada, salida=None, calidad=80, lossless=False):
if salida is None:
salida = os.path.splitext(entrada)[0] + ".webp"
with Image.open(entrada) as img:
img.save(salida, format='WEBP', quality=calidad, lossless=lossless, method=6)
kb_in = os.path.getsize(entrada) // 1024
kb_out = os.path.getsize(salida) // 1024
print(f"{entrada}: {kb_in} KB -> {kb_out} KB")
a_webp("foto.jpg")
# PNG con transparencia a WebP
with Image.open("logo.png") as img:
img.save("logo.webp", format="WEBP", quality=80)
```
## Comparación de calidad JPG
```python
from PIL import Image
import os
img = Image.open("original.jpg")
for calidad in [95, 85, 75, 60, 40]:
ruta = f"calidad_{calidad}.jpg"
img.save(ruta, format='JPEG', quality=calidad, optimize=True)
kb = os.path.getsize(ruta) // 1024
print(f"Calidad {calidad:3d}: {kb:4d} KB")
img.close()
```
Resultado típico (foto 2 MB original):
```
Calidad 95: 1850 KB
Calidad 85: 980 KB <- equilibrio recomendado
Calidad 75: 640 KB
Calidad 60: 380 KB
Calidad 40: 210 KB (degradación visible)
```
## Batch (carpeta completa)
```python
from PIL import Image
import os
def batch_comprimir(carpeta_in, carpeta_out, ancho_max=1920, calidad=85, formato="WEBP"):
extensiones = {'.jpg', '.jpeg', '.png', '.bmp', '.tiff', '.webp'}
os.makedirs(carpeta_out, exist_ok=True)
archivos = [f for f in os.listdir(carpeta_in)
if os.path.splitext(f)[1].lower() in extensiones]
print(f"Procesando {len(archivos)} imagenes...")
for nombre in archivos:
ruta_in = os.path.join(carpeta_in, nombre)
nombre_out = os.path.splitext(nombre)[0] + '.' + formato.lower()
ruta_out = os.path.join(carpeta_out, nombre_out)
try:
with Image.open(ruta_in) as img:
if img.width > ancho_max:
ratio = ancho_max / img.width
img = img.resize((ancho_max, int(img.height * ratio)), Image.LANCZOS)
if formato in ('JPEG', 'JPG') and img.mode in ('RGBA', 'P'):
img = img.convert('RGB')
img.save(ruta_out, format=formato, quality=calidad, optimize=True)
kb_in = os.path.getsize(ruta_in) // 1024
kb_out = os.path.getsize(ruta_out) // 1024
print(f" OK {nombre} ({kb_in} KB) -> {nombre_out} ({kb_out} KB)")
except Exception as e:
print(f" ERROR {nombre}: {e}")
batch_comprimir("fotos_originales/", "fotos_web/", formato="WEBP")
```
## Thumbnails cuadrados
```python
from PIL import Image
def thumbnail_cuadrado(entrada, salida, tam=400):
with Image.open(entrada) as img:
img.thumbnail((tam * 2, tam * 2), Image.LANCZOS)
lienzo = Image.new('RGB', (tam, tam), (255, 255, 255))
x = (tam - img.width) // 2
y = (tam - img.height) // 2
lienzo.paste(img, (x, y))
lienzo.save(salida, quality=85)
thumbnail_cuadrado("producto.jpg", "thumbnail.jpg", 400)
```
## Marca de agua
```python
from PIL import Image, ImageDraw, ImageFont
def marca_agua(entrada, salida, texto="(c) Mi Marca"):
with Image.open(entrada).convert('RGBA') as img:
capa = Image.new('RGBA', img.size, (0, 0, 0, 0))
draw = ImageDraw.Draw(capa)
try:
fuente = ImageFont.truetype("arial.ttf", 36)
except:
fuente = ImageFont.load_default()
bbox = draw.textbbox((0, 0), texto, font=fuente)
x = img.width - (bbox[2] - bbox[0]) - 20
y = img.height - (bbox[3] - bbox[1]) - 20
draw.text((x, y), texto, fill=(255, 255, 255, 180), font=fuente)
Image.alpha_composite(img, capa).convert('RGB').save(salida, quality=90)
marca_agua("foto.jpg", "foto_marca.jpg")
```
## Tabla de formatos
| Formato | Compresion | Transparencia | Animacion | Mejor para |
|---|---|---|---|---|
| **JPEG** | Lossy | No | No | Fotografias |
| **PNG** | Lossless | Si | No | Logos, capturas |
| **WebP** | Lossy/Lossless | Si | Si | Web moderna |
| **AVIF** | Lossy/Lossless | Si | Si | Mejor compresion |
| **GIF** | Lossless 8bit | Si (1-bit) | Si | Animaciones simples |
## Optimizacion online
Para comprimir imagenes sin instalar Python, KaijuConverter convierte entre JPG, PNG, WebP y mas formatos directamente en el navegador.
Guía