Firma de imágenes

Los curiosos que frecuenten mi web habrán visto que las últimas fotos que he publicado contienen una pequeña y discreta firma visual en la esquina inferior derecha.

La cuestión es que me he encontrado fotos mías en páginas sobre fantasmas (glup) o, peor aún, en una web oficial de la Junta de Andalucía. Los emails al respecto siempre giran sobre lo mismo: "no sabíamos que no se podían usar". En fin. El logo se puede eliminar pero, al menos, no se podrá decir que se eliminó "por error" :-)

A nivel tecnológico, utilizo ZOPE y el producto ExtFile. ExtFile almacena la imagen original, a toda resolución, y genera una pequeña imagen "preview", que es lo que se muestra en la página web si no tienes permiso para ver las imágenes en grande, que será lo habitual. La firma irá en esas "previews" para no estropear el original, al que normalmente no tendrás acceso.

En el artículo anterior ZOPE: Extender "ExtImage" para añadir un "hook" al crear una imagen "preview" explico cómo modificar el producto ExtFile para añadir un "hook" arbitrario a la hora de crear las imágenes "preview": creo un "método externo" en ZOPE, con el nombre ExtImagePREVIEW, que invoca la función jcea del siguiente código:

 # -*- coding: utf-8 -*-

 from PIL import Image, ImageFont, ImageDraw, ImageFilter, ImageEnhance

 def firma(img, texto) :
   sx, sy = img.size
   f = ImageFont.truetype("/home/zope/zope-instance.X/Extensions/trebuc.ttf", size=9)
   ssx, ssy = f.getsize(texto)

   img2 = img.crop((sx-ssx, int(sy-ssy*2.5), sx, int(sy-ssy*0.5)))

   img2 = img2.histogram()
   rojo = verde = azul = 0
   rs, gs, bs = img2[:256], img2[256:512], img2[512:]
   r, g, b = float(sum(rs)), float(sum(gs)), float(sum(bs))
   rojo = sum((i*v for i,v in enumerate(rs)))/r if r else 0
   verde = sum((i*v for i,v in enumerate(gs)))/g if g else 0
   azul = sum((i*v for i,v in enumerate(bs)))/b if b else 0
   brillo = (rojo*299 + verde*587 + azul*114)/1000.0
   if brillo < 16 :
       color = (48, 48, 48)
       brillo = 48
   elif brillo < 48 :
       brillo = 48/brillo
       color = (rojo*brillo, verde*brillo, azul*brillo)
       brillo = 48
   else :
       color = (rojo, verde, azul)

   color = (int(round(color[0])), int(round(color[1])), int(round(color[2])))
   img3 = Image.new("L", (ssx, 2*ssy), color=0)
   draw3=ImageDraw.Draw(img3)
   draw3.text((0, 0.5*ssy), texto, font=f, fill=255)
   img3=img3.filter(ImageFilter.GaussianBlur(5))
   e = ImageEnhance.Brightness(img3)
   img3 = e.enhance(5)
   e = ImageEnhance.Brightness(img3)
   img3 = e.enhance(1-(brillo-48)/414.0)
   draw3=ImageDraw.Draw(img3)
   draw3.text((0, 0.5*ssy), texto, font=f, fill=255)

   img4 = Image.new("RGB", (ssx, 2*ssy), color=0)
   draw4=ImageDraw.Draw(img4)
   draw4.text((0, 0.5*ssy), texto, font=f, fill=color)

   img.paste(img4, (sx-ssx, int(sy-ssy*2.5)), img3)

   return img

 def jcea(self, im, maxx, maxy, ratio, makePreview) :
   im = makePreview(im, maxx, maxy, ratio)
   return firma(im, u"  © jcea  ")

La función jcea llama a la rutina original de ExtFile para generar la "preview" como siempre y luego le estampa la firma encima. Para que la firma sea poco llamativa será pequeña y se camuflará un poco, mimetizando los colores y el brillo local de la imagen.

La primera parte del código (líneas 6-28) toma el trozo de la imagen sobre la que vamos a escribir la firma y calcula su color y su brillo medio. Es de notar que si el brillo es muy bajo, usaremos un color gris y un brillo local más alto, para que la firma sea visible. Sino dibujaríamos negro sobre negro.

En las líneas 30-40 se genera un "blur" del texto de la firma y se ajusta su transparencia en función del brillo de la imagen original. Cuanto más brillante, más transparente.

En las líneas 42-46, se dibuja el texto y su "blur" con el color apropiado a la zona y se devuelve la imagen así modificada.

La firma podría "pregenerarse" y evitar realizar todos esos cálculos para cada imagen. Pero, en realidad, es un proceso muy rápido y que solo se ejecuta cuando se suben fotografías nuevas, no cuando se visualizan. Una vez en caché, la carga de la fuente de caracteres apenas supone 0.11 milisegundos y la generación completa de la firma 2.2 milisegundos. En mi entorno no es un problema y no vale la pena optimizarlo más.

extimage1   extimage2

En este ejemplo puede verse tanto la mimetización del color medio local como el ajuste de la transparencia del "blur" en función del brillo local de la imagen. El objetivo es obtener una firma pequeña y discreta, pero visible. Misión cumplida.

Algunos ejemplos:

Libero el código mostrado como "dominio público". Haz con el lo que quieras, aunque si haces algo chulo me gustaría que me lo contases :-).