Agregar Marca de Agua a Imágenes con Python y Pillow
Pillow permite insertar marcas de agua de texto o imagen en fotos y documentos de forma automática. Ideal para proteger fotografías, firmar capturas y procesar lotes de imágenes.
Instalación
pip install pillow
Marca de Agua de Texto Simple
from PIL import Image, ImageDraw, ImageFont
def texto_watermark(entrada, salida, texto, opacidad=128):
img = Image.open(entrada).convert("RGBA")
capa = Image.new("RGBA", img.size, (255,255,255,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)
ancho_texto = bbox[2] - bbox[0]
alto_texto = bbox[3] - bbox[1]
# Centrar en la imagen
x = (img.width - ancho_texto) // 2
y = (img.height - alto_texto) // 2
draw.text((x, y), texto, font=fuente, fill=(255,255,255,opacidad))
resultado = Image.alpha_composite(img, capa).convert("RGB")
resultado.save(salida)
print(f"Marca de agua agregada: {salida}")
texto_watermark("foto.jpg", "foto_firmada.jpg", "© Mi Empresa 2024")
Marca de Agua en Esquina con Sombra
from PIL import Image, ImageDraw, ImageFont
def watermark_esquina(entrada, salida, texto, margen=20):
img = Image.open(entrada).convert("RGBA")
capa = Image.new("RGBA", img.size, (255,255,255,0))
draw = ImageDraw.Draw(capa)
try:
fuente = ImageFont.truetype("arial.ttf", 28)
except:
fuente = ImageFont.load_default()
bbox = draw.textbbox((0,0), texto, font=fuente)
tw = bbox[2] - bbox[0]
th = bbox[3] - bbox[1]
x = img.width - tw - margen
y = img.height - th - margen
# Sombra (desplazada 2px)
draw.text((x+2, y+2), texto, font=fuente, fill=(0,0,0,160))
# Texto principal
draw.text((x, y), texto, font=fuente, fill=(255,255,255,200))
Image.alpha_composite(img, capa).convert("RGB").save(salida)
watermark_esquina("paisaje.jpg", "paisaje_firmado.jpg", "© 2024 MiFotografía")
Marca de Agua Diagonal (Patrón Repetido)
from PIL import Image, ImageDraw, ImageFont
import math
def watermark_diagonal(entrada, salida, texto, espaciado=200, angulo=30, opacidad=60):
img = Image.open(entrada).convert("RGBA")
capa = Image.new("RGBA", img.size, (255,255,255,0))
draw = ImageDraw.Draw(capa)
try:
fuente = ImageFont.truetype("arial.ttf", 24)
except:
fuente = ImageFont.load_default()
for y in range(-img.height, img.height*2, espaciado):
for x in range(-img.width, img.width*2, espaciado):
# Rotar texto con una capa temporal
txt_img = Image.new("RGBA", (300, 50), (255,255,255,0))
txt_draw = ImageDraw.Draw(txt_img)
txt_draw.text((0,0), texto, font=fuente, fill=(128,128,128,opacidad))
txt_img = txt_img.rotate(angulo, expand=True)
capa.paste(txt_img, (x, y), txt_img)
Image.alpha_composite(img, capa).convert("RGB").save(salida)
watermark_diagonal("documento.jpg", "documento_protegido.jpg", "CONFIDENCIAL")
Marca de Agua con Imagen (Logo)
from PIL import Image
def watermark_logo(imagen_base, logo_path, salida, posicion="abajo-derecha", escala=0.15):
base = Image.open(imagen_base).convert("RGBA")
logo = Image.open(logo_path).convert("RGBA")
# Escalar logo
nuevo_ancho = int(base.width * escala)
ratio = nuevo_ancho / logo.width
nuevo_alto = int(logo.height * ratio)
logo = logo.resize((nuevo_ancho, nuevo_alto), Image.LANCZOS)
margen = 20
posiciones = {
"arriba-izquierda": (margen, margen),
"arriba-derecha": (base.width - logo.width - margen, margen),
"abajo-izquierda": (margen, base.height - logo.height - margen),
"abajo-derecha": (base.width - logo.width - margen,
base.height - logo.height - margen),
"centro": ((base.width - logo.width) // 2,
(base.height - logo.height) // 2),
}
x, y = posiciones.get(posicion, posiciones["abajo-derecha"])
capa = Image.new("RGBA", base.size, (0,0,0,0))
capa.paste(logo, (x, y), logo)
Image.alpha_composite(base, capa).convert("RGB").save(salida)
print(f"Logo insertado en: {salida}")
watermark_logo("foto.jpg", "logo.png", "foto_con_logo.jpg")
Proceso por Lotes
from PIL import Image, ImageDraw, ImageFont
from pathlib import Path
def watermark_lote(carpeta, texto, formato_salida="jpg"):
entrada = Path(carpeta)
salida = entrada / "con_marca"
salida.mkdir(exist_ok=True)
imgs = list(entrada.glob("*.jpg")) + list(entrada.glob("*.png"))
for i, ruta in enumerate(imgs, 1):
img = Image.open(ruta).convert("RGBA")
capa = Image.new("RGBA", img.size, (255,255,255,0))
draw = ImageDraw.Draw(capa)
try:
fuente = ImageFont.truetype("arial.ttf", max(20, img.width//40))
except:
fuente = ImageFont.load_default()
bbox = draw.textbbox((0,0), texto, font=fuente)
x = img.width - (bbox[2]-bbox[0]) - 15
y = img.height - (bbox[3]-bbox[1]) - 15
draw.text((x+2,y+2), texto, font=fuente, fill=(0,0,0,120))
draw.text((x,y), texto, font=fuente, fill=(255,255,255,180))
out = salida / (ruta.stem + "." + formato_salida)
Image.alpha_composite(img, capa).convert("RGB").save(str(out))
print(f" [{i}/{len(imgs)}] {out.name}")
print(f"Lote completado: {len(imgs)} imágenes en {salida}")
watermark_lote("fotos/", "© MiFotografía.com")
Recurso Adicional
Para convertir imágenes entre JPG, PNG, WebP y otros formatos sin instalar nada, usa KaijuConverter — gratis y sin registro.
Conversiones relacionadas
Lo más habitual al trabajar con imágenes son estas direcciones de conversión: