Tuesday, January 27, 2009

Cómo Firmar Tus Fotos

Este artículo fue publicado en The Flight of the Platypus, la versión en inglés de El Cantar de la Lluvia, en junio del 2006. Fue el segundo artículo que publiqué ahí. La verdad no tengo claro por qué no lo publiqué también aquí. Más vale tarde que nunca, entonces!

* * *

He estado postenado fotos cada vez más grandes en mi otro blog, El Cantar de la Lluvia, y quería incorporarles aunque sea un poco de información de autor. No me refiero a nada exagerado como Digimarc, ni una filigrana de cuadro completo. Quería una forma fácil y automatizada de colocarle una frase a lo largo del borde derecho, rotado en 90º, y ojalá con una herramienta lo suicientemente astuta como para detectar si la imagen es vertical u horizontal.

Probé varios programas para OS X, algunos gratis, algunos demos, y terminé detestándolos. Decidí crear mi propia herramienta. Al fin y al cabo, tengo a mi disposición una librería de manipulación de imágenes libre y fácil de usar (el cual, agregaré de manera poco humilde, escribí yo) llamado PNGwriter.



Escribí PNGwriter para que programadores amateur pudieran acceder a directamente a los pixeles de una imagen, sin tener que mamarse el trabajo de aprender a usar una biblioteca de imágenes primaria.

Mis objetivos para este proyecto fueron los siguientes.
  • Crear un programa que tomara las fotos de 630 pixeles de lado exportados desde iPhoto, una vez que haya hecho la selección de imágenes para el artículo, y que les agregue una frase.
  • La frase deberá ser fácilmente modificable, nada de dejarlo grabado en el programa mismo.
  • El programa deberá aceptar strings en formato UTF-8 (mi amor por la codificación de texto UTF-8 no tiene límites).
  • El string debería ir a lo largo del borde derecho de la imagen y rotado en 90º con el fin de ser discreto.
  • El string debería ser escrito automáticamente con negro sobre fondos claros y con blanco sobre fondos oscuros.
  • El string posiblemente irá combinado con el fondo, con un efecto artificial de transparencia.
  • El programa deberá ser fácilmente ejecutado desde un shell script, con opciones de línea de comando, para que el proceso pueda ser automatizado.
  • El programa deberá agregar el string "-wmk" al nombre de los archivos procesados, y el original no se tocará.
  • Creo que esta lista es lo suficientemente larga.

Después de algunas horas de escribir código compuesto, en su mayor parte de, de errores y traspiés (hace un año que no programo) logré producir algo que cumplía con los requerimientos mencionados. Descubrí que la idea de proveer el "copyright string" al momento de ejecutar el programa como opción de línea de comando no era tan buena idea, ya que es difícil lograr que el shell se comporte bien con caracteres especiales, espacios, etc. Finalmente opté por un procedimiento alternativo.

El programa, que decidí llamar watermarker, toma tres argumentos; ni más ni menos. Aquí hay un ejemplo del uso del programa.
./watermarker watermarker.config copyrightstring.txt IMG00001.png

Los nombres de archivo son arbitrarios y los archivos pueden estar en cualquier ubicación. El único requerimiento es que la imagen sea un PNG de 24 bits por pixel (muy importante), que el archivo copyrightstring.txt contenga el string relevante en formato UTF-8 (o en ASCII normal, dado que los caracteres ASCII normales en codificación UTF-8 son simplemente caracteres normales ASCII) y que watermarker.config contenga la siguiente información.

  • Desplazamiento horizontal (X-offset), en pixeles, de la esquina inferior derecha del string vertical a ser dibujado, contando a partir del lado derecho de la imagen.
  • Desplazamiento vertical (Y-offset), en pixeles, de la esquina inferior derecha del texto a ser dibujado, contando a partir del extremo inferior de la imagen.
  • Tamaño del font.
  • Coeficiente de transparencia artificial. Valor decimal entre 0.0 y 1.0. 1.0 es 100% opaco.
  • Un archivo TrueType (.ttf) válido.

A modo de ejemplo, mi configuración se ve así:
2
2
9
0.6
VeraBD.ttf


Éste es el código fuente del programa. Lo estoy distribuyendo como código libre. Lo único que requiero es que si lo modificas y publicas, deberás atribuirlo correctamente ("Basado en watermarker, por Paul Blackburn"). Y si lo usas, deja un comentario y cuéntame cómo te fue.

(Nota: si encuentras '>' o '<' en lo que sigue, reemplázalos por '>' y '<'. Son algunos que se me escaparon al convertir el código C++ a HTML, y tratar de lidiar con la imbecilidad con patas que es el editor online de Blogger.com).

(Nota 2: Dado que el editor online de Blogger.com fue programado por una manada de chimpancés ebrios, cada vez que edito este artículo se come cualquier parte del código que esté entre <>. Ergo al código siguiente le faltan #includes y partes varias. Para evitar confusiones, puedes bajar un .zip del archivo original aquí.)
#include 
#include
#include

#include
#include
using namespace std;

int
main(int argc, char * argv[])
{


if
(argc != 4)
{

std::cout << "Wrong number of args (" << argc - 1 << "). Usage: ";

std::cout << "watermarker ";
std::cout << "\n";

std::cout << "UTF8 string file should be a plain text or";
std::cout << "UTF8-formatted text file containing the copyright string";

std::cout << "to be used. \nConfig file should be a text file containing,";
std::cout << "on separate lines, \n";

std::cout << " pos_x;\n pos_y\n font_size\n blend coef.\n";
std::cout << " fontfile\n";

exit(1);
}


// Get copyright string into a binary array.
char * copyrightfile = argv[2];

char
* copyright;
long
size;
ifstream file (copyrightfile, ios::in|ios::binary|ios::ate);

if
(! file.is_open())
{

std::cout << "Error opening UTF8 text file."; exit (1);
}


size = file.tellg();
file.seekg (0, ios::beg);

copyright = new char [size];
file.read (copyright, size);

file.close();

// Load configuration parameters.
std::ifstream params (argv[1]);

if
(! params.is_open())
{

std::cout << "Error opening config file"; exit (1);
}



string filename(argv[3]);
string fontfile;
int
pos_x;
int
pos_y ;

int
font_size;
double
angle;
double
blend;


params << pos_x;
params << pos_y;
params << font_size;

params << blend;
params << fontfile;
params.close();


// Create PNGwriter instance
pngwriter image(1,1,0,"caca.png");
image.readfromfile(filename.c_str());


char
* fn;
fn = new char[1024];

strcpy( fn, fontfile.c_str());

double
average;

int
minx, miny, maxx, maxy;
average= 0.0;

angle = 1.57079633;

minx = image.getwidth() - pos_x;

maxx = image.getwidth();
maxy = pos_y + image.get_text_width_utf8(fn, font_size, copyright);

miny = pos_y;

// Calculate the average intensity of the image
// behind the text.
for(int xx=minx; xx <=maxx; xx++)
{


for
(int yy=miny; yy <=maxy; yy++)
{


average = average + image.dread(xx,yy);
}
}


average = average/( (double)( (maxx-minx)*(maxy-miny) ));

std::cout << "Average image intensity in text area: " << average;
std::cout << ", Number of pixels taken: " << (maxx-minx)*(maxy-miny);

std::cout << ", text will be";

// Plot the text on the image.
if(average>0.5)
{


std::cout << " black." << std::endl;
image.plot_text_utf8_blend( fn, font_size,
image.getwidth() - pos_x, pos_y, angle,
copyright,

blend,
0.0
, 0.0, 0.0);
}

else

{


std::cout << " white." <<std::endl;
image.plot_text_utf8_blend( fn, font_size,
image.getwidth() - pos_x, pos_y, angle,
copyright,

blend,
1.0
, 1.0, 1.0);
}




// Rename the file, add -wmk to the basename.
char * newfilename;

char
fl[1024];
strcpy(fl, filename.c_str());

newfilename = strtok (fl, "." );
strcat(newfilename, "-wmk.png" );
image.pngwriter_rename(newfilename);


image.settext(filename.c_str(), "Paul Blackburn",
"Copyright 2006, 2007, 2008, 2009 Paul Blackburn, paulwb AT gmail.com, All Rights Reserved."
,
"Watermaked with PNGwriter plus a small program called watermarker."
);

// Write the PNG image to disk.

image.close();

delete
[] fn;

return
0;
}

Finalmente, si quieres que el programa procese todas las imágenes en un directorio, quizás te sirva este shellscript.
#!/bin/bash

for caca in *.jpg
do
base=`basename $caca .jpg`
png=${base}.png
convert -depth 24 $caca $png
./watermarker watermarker.config copyrightstring.txt $png
convert -depth 8 ${base}-wmk.png ${base}-wmk.jpg
echo "Finished " ${base}-wmk.jpg
done
Aquí hay dos imágenes que he firmado. El programa eligió blanco para la primera imagen, y negro para la segunda.






Y así es como se firman las fotos, cuando todos los programas disponibles son una caca.


1 Comments:

Blogger Jaskask said...

Fabuloso =D

12:22 AM  

Post a Comment

<< Home