16OctEfecto “Fade Out” con C++ e ImageMagick
Llevo algunas semanas realizando una aplicación en C++ con las librerías de tratamiento de imagen de ImageMagick y me he decidido a hacer un tutorial sobre algo que he aprendido desarrollando dicha aplicación. En este breve tutorial pretendo explicaros cómo implementar el conocido efecto “desvanecer” (Fade Out) utilizando el lenguaje de programación C++ y trabajando sobre Linux.
Voy a saltarme la explicación sobre cómo instalar las librerías de desarrollo de ImageMagick ya que varía ligeramente de una distribución a otra y no suele ser muy complicado (teniendo en cuenta que ImageMagick suele estar presente en los repositorios más comunes y accesible desde gestores de paquetes como Synaptic). De todas formas si os surge alguna duda utilizad los comentarios.
Para simplificar la aplicación, ésta carecerá de interfaz gráfica y se ejecutará por terminal. El input del programa serán dos imágenes del mismo tamaño y un número entero positivo que indicará el número de imágenes intermedias que queremos que genere el programa como resultado.
Antes de empezar a poneros código fuente os comentaré la estructura de directorios del proyecto. Llamaremos al proyecto SFFadeOut (de Segmentation Fault Fade Out), por lo que la carpeta contenedora tendrá ese nombre. Dentro crearemos 4 directorios más:
- /bin: para los ficheros binarios
- /inc: para los headers (.h) a incluir en el proyecto
- /obj: para los .o
- /src: para los ficheros de código fuente
Ahora sí, ¡veamos el código!
En primer lugar os dejo el código de los 2 ficheros que conforman la clase Imagen. Esta clase se encargará de almacenar la información de los píxeles así como de tener algunos métodos y operadores para facilitar el trabajo. Primero os dejo el fichero Imagen.h que estará en /SFFadeOut/inc:
#ifndef IMAGEN_H
#define IMAGEN_H
class Imagen
{
public:
int dim[2];
double * datos;
Imagen();
Imagen(int fil, int col);
Imagen(int fil, int col, double val);
~Imagen();
void operator=(Imagen &im2);
int fils(){return dim[0];}
int cols(){return dim[1];}
inline double & operator()(int i, int j)
{
return(datos[i*dim[1]+j]);
};
};
Imagen & lee(char * archima, int campo);
int compara_dims(Imagen & im1, Imagen & im2);
void escribe(char * archima, Imagen & imR, Imagen & imG, Imagen & imB);
#endif
En éste fichero se encuentra la definición estática de la clase. Podéis ver los atributos de la clase, los constructores, un par de operadores y métodos propios de la clase. A continuación os dejo su correspondiente Imagen.cpp:
#include <Imagen.h>
#include <stdio.h>
#include <math.h>
Imagen::Imagen()
{
dim[0]=0;
dim[1]=0;
datos=new double[1];
}
Imagen::Imagen(int fil, int col)
{
dim[0]=fil;
dim[1]=col;
datos=new double[fil*col];
}
Imagen::Imagen(int fil, int col, double val)
{
dim[0]=fil;
dim[1]=col;
datos=new double[fil*col];
for(int i=0; i<fil*col;i++)
datos[i]=val;
}
Imagen::~Imagen()
{
delete[] datos;
}
void Imagen::operator=(Imagen &im2)
{
// este operador no duplica la memoria //
int fil2=im2.fils();
int col2=im2.cols();
if(dim[0]!=fil2 || dim[1]!=col2)
{
dim[0]=fil2;
dim[1]=col2;
delete[] datos;
datos=new double[fil2*col2];
}
int largo=dim[0]*dim[1];
for(int i=0; i<largo; i++)
datos[i]=im2.datos[i];
return;
}
Si os habéis dado cuenta, hay algunas funciones definidas en el header que no están implementadas en el .cpp. El motivo es que vamos a crear otro fichero llamado inout.cpp en el que incluir las funciones de lectura y escritura de imágenes (y también la función para comparar las dimesiones de las imágenes).
#include <Imagen.h>
#include <stdio.h>
#include <Magick++.h>
#include <iostream>
using namespace std;
using namespace Magick;
Imagen & lee(char * archima, int campo)
{
string filename(archima);
Image image;
image.read(filename);
Geometry g = image.size();
int ancho = g.width();
int alto= g.height();
double c=1.0/257.0;
Imagen * im=new Imagen(alto,ancho,(double)0.0);
for(int i=0;i<alto;i++)
for(int j=0;j<ancho;j++)
switch(campo)
{
case 0:
(*im)(i,j)=c*( double (image.pixelColor(j,i).redQuantum()));
break;
case 1:
(*im)(i,j)=c*( double(image.pixelColor(j,i).greenQuantum()));
break;
case 2:
(*im)(i,j)=c*( double(image.pixelColor(j,i).blueQuantum()));
break;
default:
fprintf(stderr,"\n Error en inout.cpp al leer archivo \n");
break;
}
return(*im);
};
int compara_dims(Imagen & im1, Imagen & im2)
{
if(im1.fils()!=im2.fils() | im1.cols()!=im2.cols())
return 0;
return 1;
};
void escribe(char * archima, Imagen & imR, Imagen & imG, Imagen & imB)
{
int ancho=imR.cols();
int alto=imR.fils();
if(compara_dims(imR,imG)!=1 | compara_dims(imG,imB)!=1 )
{
fprintf(stderr,"\n Error al escribir en inout.cpp: imagenes R,G,B con distintas dimensiones. \n");
return;
}
Geometry geom(ancho,alto);
Image im(geom,"white");
for(int i=0;i<alto;i++)
for(int j=0;j<ancho;j++)
{
double R=imR(i,j);
double G=imG(i,j);
double B=imB(i,j);
R/=255.0;
G/=255.0;
B/=255.0;
ColorRGB col(R,G,B);
im.pixelColor(j,i,col);
}
im.write(archima);
return;
};
Explicar todo el código anterior es bastante complejo por lo que pretendo que lo toméis como una API que supone una capa más de abstracción sobre la librería de ImageMagick. Ahora sí que os dejo la parte interesante, el fichero fadeout.cpp en el cual está el main del programa. Lo primero que realiza es comprobar si son correctos los parámetros de entrada y en caso contrario alertar al usuario. A continuación lee las imágenes y las almacena en objetos de la clase Imagen anteriormente detallada. Crea también un objeto para las imágenes resultantes e intera para cada píxel de cada una de las imágenes resultantes n, realizando el fundido (FadeOut).
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <sstream>
#include <Imagen.h>
#include <Magick++.h>
#include <iostream>
using namespace std;
using namespace Magick;
void uso()
{
fprintf(stderr,"Uso: prueba imagen0 imagen1 n \n");
exit(1);
};
char * getImagename(int i){
stringstream temp;
temp << "imagen_resultante_" << i << ".tiff";
string resultName = temp.str();
char *cstr = new char [resultName.size()+1];
strcpy (cstr, resultName.c_str());
return cstr;
}
main(int argc,char **argv)
{
//verifica que la cantidad de argumentos es correcta
if(argc<4)
uso();
//almacena los argumentos en sus correspondientes variables
argv++;
char * nimagen0 =*argv++;
char * nimagen1 =*argv++;
int n=atoi(*argv++);
//lee la primera imagen de entrada
Imagen im0R=lee(nimagen0,0);
Imagen im0G=lee(nimagen0,1);
Imagen im0B=lee(nimagen0,2);
//lee la segunda imagen de entrada
Imagen im1R=lee(nimagen1,0);
Imagen im1G=lee(nimagen1,1);
Imagen im1B=lee(nimagen1,2);
int cont;
int ancho=im0R.cols();
int alto=im0R.fils();
// crea un objeto Imagen para la imagen resultante
Imagen resultR(ancho, alto);
Imagen resultG(ancho, alto);
Imagen resultB(ancho, alto);
// la primera imagen resultante es la imagen de inicio
escribe("imagen_resultante_0.tiff",im0R,im0G,im0B);
// bucle para generar n imágenes resultantes
for(cont=0; cont <n; cont++){
// doble bucle que recorre los píxeles
for(int i=0;i<alto;i++){
for(int j=0;j<ancho;j++){
// realiza el fundido
resultR(i,j) = (float)(((float)(n+1)-(cont+1)) / (float)(n+1)) * im0R(i,j) + (float)((float)(cont+1) / (float)(n+1)) * im1R(i,j);
resultG(i,j) = (float)(((float)(n+1)-(cont+1)) / (float)(n+1)) * im0G(i,j) + (float)((float)(cont+1) / (float)(n+1)) * im1G(i,j);
resultB(i,j) = (float)(((float)(n+1)-(cont+1)) / (float)(n+1)) * im0B(i,j) + (float)((float)(cont+1) / (float)(n+1)) * im1B(i,j);
}
}
// escribe la imagen resultante n
escribe(getImagename(cont+1),resultR,resultG,resultB);
}
// escribe la imagen final
escribe(getImagename(cont+1),im1R,im1G,im1B);
fprintf(stdout,"\n\n Final del programa \n\n");
}
El cuerpo de los bucles es el que realiza el fundido propiamente dicho aplicando la fórmula:
Fórmula para implementar el fundido.
Para facilitar un poco más la tarea de compilar el programa os dejo también el Makefile que yo utilizo:
HOME = .. TARGETS = $(SFFADEOUT) OBJECTS = $(OBJSFFADEOUT) ############################### # esto es general CPP = g++ incdir1 = $(HOME)/inc incdir2 = /usr/include/ImageMagick #incdir2 = /usr/include/GraphicsMagick SRC= $(HOME)/src OBJ = $(HOME)/obj bindir=$(HOME)/bin INCLUDES = -I$(incdir2) -I$(incdir1) CPPFLAGS = -g -O3 LIBS = -DHAVE_CONFIG_H -D_REENTRANT -D_FILE_OFFSET_BITS=64 -I/usr/include/magick -I/X11 -L/usr/lib -L/usr/X11R6/lib -lMagick++ -ltiff -ljpeg -lpng -lbz2 -lz -lpthread -lm -ldl -lxml2 ########################################## # SFFADEOUT SFFADEOUT = sffadeout OBJSFFADEOUT = fadeout.o Imagen.o inout.o installsffadeout: $(bindir)/$(SFFADEOUT) $(bindir)/sffadeout: $(OBJ)/sffadeout -rm $(bindir)/sffadeout cp -p $(OBJ)/sffadeout $(bindir)/sffadeout sffadeout: $(OBJ)/sffadeout $(OBJ)/sffadeout: $(addprefix $(OBJ)/, $(OBJSFFADEOUT)) $(CPP) $(INCLUDES) $(CPPFLAGS) -o $(addprefix $(OBJ)/,$(SFFADEOUT)) $(addprefix $(OBJ)/, $(OBJSFFADEOUT)) -lpthread $(LIBS) $(MesaLibs) ######################################### # reglas generales $(OBJ)/%.o : %.cpp $(CPP) -c $(INCLUDES) $(CPPFLAGS) $&lt; -o $@ .PHONY: prueba all: directorios $(TARGETS) clean: -rm $(TARGETS) -rm $(OBJ)/*.o cleanall: clean build: clean all install: $(subst $(OBJ),$(bindir),$(TARGETS)) directorios: @if [ ! -d $(bindir) ]; then mkdir $(bindir); fi @if [ ! -d $(OBJ) ]; then mkdir $(OBJ); fi
Utilizando el comando make sólo deberéis navegar hasta la ruta adecuada y ejecutar el Makefile:
make -k sffadeout
Recordad que para que todo funcione correctamente debéis ser fieles a la estructura que os he comentado al inicio del post. Es decir, al final debéis tener una estructura de:
- /SFFadeOut
- /bin
- /inc
- Imagen.h
- /obj
- /src
- fadeout.cpp
- Imagen.cpp
- inout.cpp
- Makefile
Una vez compilado necesitáis un par de imágenes del mismo tamaño para provar si realmente funciona. Siendo fiel a mis anteriores artículos sobre procesamiento de imagen os animo a provar con la imagen de Lenna y una imagen negra.
Podéis ejecutar el programa con la instrucción (si las imágenes están en el directorio /SFFadeOut):
./obj/sffadeout ../lenna.png ../negro.jpg 4
El resultado con n=4 debería dar las siguientes imágenes:
Os dejo además un zip con todo el código estructurado en los directorios correctamente y con las imágenes de prueba que os he comentado. Para obtener mejores resultados probad con una n más alta y si queréis ver el fundido en acción os recomiendo crear un GIF animado.
Espero que os haya parecido interesante y no demasiado complicado. Seguro que hay fallos y mejoras así que os animo a todos a que expreséis vuestra opinión, sugerencias, problemas y demás en los comentarios.
Salu2!
Fuentes
Marcelo Bertalmío – http://www.tecn.upf.es/~mbertalmio/

One Response and Counting...
Bitacoras.com
October 16th 2009Información Bitacoras.com…
Valora en Bitacoras.com: No hay resumen disponible para esta anotación…