Universitat Oberta de Catalunya

Efecto dinámico de Puntillismo en Imágenes vía Programación Processing

Vamos a realizar un sencillo programa Processing que a) Cargará una imagen de disco duro y b) Le aplicará un efecto de “Puntillismo” siempre y cuando pulsemos una vez uno de los botones del ratón (siempre dentro de la imagen). En la figura 1 podemos entenderlo mejor.

LUT2

Figura 1. Si pulsamos una de las teclas del ratón y después lo movemos… observaremos un curioso efecto de “Puntillismo. Imagen cortesía de http://www.freeimageslive.com/.

Puede observarse que la imagen de la izquierda es la original. Tras hacer clic con el ratón y a medida que nos desplazamos, la imagen sustituye sucesivamente a sus píxeles por pequeños círculos del mismo color. Se crea una especie de efecto de filtro como si, por ejemplo, estuviéramos mirando desde el otro lado de un curioso cristal tallado.

Empecemos por aprender cómo se carga una imagen en Processing, gracias al código 1.

// A PImage object acts as the container for the test image
PImage myImage;
// 3 integer variables that we are in need of
int ellipseMagnitude = 30;
int showMode = 2;
int pixelLocation;

// The Setup Function that initializes everything
void setup(){
// We setup the window with the same dimensions than our test image
size(320,240);
// We load the image into the PImage object
myImage = loadImage("image_to_test_with.jpg");
}

Código 1. Variables e inicialización de nuestro programa.

Creamos diversas variables para controlar el tamaño de nuestros círculos (ellipseMagnitude), calcular la ubicación de nuestros píxeles dentro de la imagen que cargaremos (pixelLocation) y decidir entre activar o no el efecto que estamos implementando (showMode). También definimos un objeto Pimage de Processing que será el encargado de almacenar la imagen en memoria, previa carga de ésta desde disco duro. Dentro de nuestra función de setup()creamos una ventana de 320 por 240 píxeles, dado que ésta es la resolución de nuestra imagen originalmente. A continuación cargamos la imagen, el fichero image_to_test_with.jpg que previamente habremos introducido en un subdirectorio de nombre “data” dentro del directorio de nuestro sketch Processing. Finalmente tenemos la imagen cargada dentro de nuestro objeto myImage.

El acceso a cada uno de los píxeles de la imagen así como la aplicación del efecto lo podemos observar dentro de la función draw(), en el código 2.

// The infinite loop
void draw(){
// showMode == 1 implies drawing a little ellipse at the mouse's coords
if (showMode == 1){
int x = mouseX;
int y = mouseY;
pixelLocation = x + y*myImage.width;
// Grab the RGB values from our test image
loadPixels();
float r = red(myImage.pixels[pixelLocation]);
float g = green(myImage.pixels[pixelLocation]);
float b = blue(myImage.pixels[pixelLocation]);
// We are not drawing any strokes for our ellipses
noStroke();
// Draw an ellipse at the mouse's coords location
// with it's associated pixel's color
// We apply some transparency
fill(r,g,b,150);
ellipse(x,y,ellipseMagnitude,ellipseMagnitude);
}
// showMode == 2 implies recovering the original test image
else image(myImage,0,0);
}

Código 2. Aplicación del efecto a nuestra imagen.

Cuando Processing carga una imagen dentro de un objeto PImage no lo almacena bidimensionalmente, es decir teniendo en cuenta el ancho por el alto, sino que lo hace unidimensionalmente. Se trata en definitiva de un array muy largo donde cada posición almacena el color de un píxel. Lee las sucesivas filas de la imagen y las va concatenando, tal y como podéis observar en el apartado “Pixels, pixels, and more pixels” de este tutorial. Eso implica que para acceder a un píxel en concreto de nuestra imagen, tenemos que calcular su localización dentro del objeto PImage. Para un píxel que tenga coordenadas XY en la imagen, su ubicación dentro del objeto se calculará como X + Y x Ancho de la Imagen. Y ese valor lo almacenaremos en la variable entera pixelLocation que definimos a tal efecto.

Dependiendo del valor de la variable showMode activaremos el efecto o no. Observemos que para cada iteración de la función draw() de Processing, almacenamos las coordenadas actuales del ratón (mouseX y mouseY). A continuación calculamos la ubicación del píxel que se encuentra debajo del ratón (su ubicación dentro del objeto Pimage) y después llamamos a la función de acceso a los píxeles de la imagen (loadPixels()). Para cada píxel, obtenemos los valores de sus tres componentes de color RGB (funciones red, green y blue) para asociarlas a tres variables de nueva creación (r, g y b). Nótese que éstas son de tipo real y por tanto con decimales (float) dado que las componentes de color se retornan en ese formato.

Llegados a este punto sólo nos queda fijar un círculo del mismo color que el píxel que se encuentra debajo y que actuará como su centro. Desactivamos el color para los bordes, activamos el color de nuestro círculo gracias a la función fill(r,g,b,150) y lo pintamos. El círculo tiene una cierta transparencia, como puede observarse en el cuarto parámetro de la función fill(), que hace referencia al canal alfa.

Si showMode vale 2 tendremos suficiente con utilizar la función image(myImage, 0, 0)para pintar el contenido de nuestro objeto myImage en el origen de nuestra ventana. Es decir que estamos haciendo un efecto de “reset” sobre la composición de puntos sobre la imagen que hayamos realizado hasta ese momento. Y vuelta a empezar!. Observemos que en el código 3, alternamos entre los modos 1 y 2 en nuestro programa gracias al evento/callback de clic de ratón mousePressed().

El programa completo podéis observarlo en el código 4 y os invito a que lo modifiquéis a conveniencia para obtener resultados de todo tipo.

// We toggle between showMode 1 and 2 by clicking a mouse button
void mousePressed(){
if (showMode == 1) showMode = 2;
else showMode = 1;
}

Código 3. Alternando modos gracias a 1 clic de ratón.

// A PImage object acts as the container for the test image
PImage myImage;
// 3 integer variables that we are in need of
int ellipseMagnitude = 30;
int showMode = 2;
int pixelLocation;

// The Setup Function that initializes everything
void setup(){
// We setup the window with the same dimensions than our test image
size(320,240);
// We load the image into the PImage object
myImage = loadImage("image_to_test_with.jpg");
}

// The infinite loop
void draw(){
// showMode == 1 implies drawing a little ellipse at the mouse's coords
if (showMode == 1){
int x = mouseX;
int y = mouseY;
pixelLocation = x + y*myImage.width;
// Grab the RGB values from our test image
loadPixels();
float r = red(myImage.pixels[pixelLocation]);
float g = green(myImage.pixels[pixelLocation]);
float b = blue(myImage.pixels[pixelLocation]);
// We are not drawing any strokes for our ellipses
noStroke();
// Draw an ellipse at the mouse's coords location
// with it's associated pixel's color
// We apply some transparency
fill(r,g,b,150);
ellipse(x,y,ellipseMagnitude,ellipseMagnitude);
}
// showMode == 2 implies recovering the original test image
else image(myImage,0,0);
}

// We toggle between showMode 1 and 2 by clicking a mouse button
void mousePressed(){
if (showMode == 1) showMode = 2;
else showMode = 1;
}

Código 4. Programa completo de aplicación del efecto de “Puntillismo a nuestra imagen.

Enlaces relacionados

Acerca del autor

Oscar García Pañella es Doctor en Realidad Virtual por La Salle-URL y cursó un Post Doctorado en tecnología del entretenimiento en la Carnegie Mellon University.

Ha dirigido el Grado en Ingeniería Multimedia y el Máster en Creación Multimedia y Serious Games de La Salle-URL durante más de 10 años, además de fundar el Ecosistema Creativo Media Dome del mismo centro en 2009.

Actualmente participa en la dirección y la impartición de los estudios en materia Multimedia en distintas universidades como la UOC, l’ERAM, La Salle Barcelona y Holanda, ESADE y Blanquerna y también opera como Consultor en Gamificación para varias empresas.

Deja un comentario