martedì 17 maggio 2011

Processing. Geco peloso - Furry gecko


English version below.
Una delle caratteristiche più interessanti di Processing è il fatto di avere delle funzioni predefinite per lavorare con le immagini.
Per il loro utilizzo si può consultare l'help interno all'ambiente di sviluppo (Help-> Reference).

Volevo realizzare un effetto che agisse sui bordi del soggetto di un'immagine, quindi ne ho scelta una in cui il bordo fosse abbastanza definito:


Per prima cosa bisogna caricare l'immagine nel nostro progetto attraverso il menù Sketch -> Add file..., questo crea una cartella "data" in cui viene copiata l'immagine da utilizzare.
A questo punto bisogna individuare il bordo del soggetto. La parte di codice che ci interessa è la seguente (da inserire nel blocco "draw()"):
b = loadImage("DSC05842.JPG");
image(b, 0, 0, width, height);
filter(THRESHOLD, 0.4);
Applichiamo un filtro all'immagine, attraverso una chiamata, "filter(THRESHOLD, 0.4)", alla funzione filter, con primo parametro impostato su "THRESHOLD", che individua appunto i contorni degli oggetti nella foto, trasformando l'immagine in bianco e nero. Giocando un po' con il secondo parametro (che va da 0 a 1, default 0.5) possiamo migliorare la resa per la nostra fotografia. Il risultato è questo:

loadPixels();
for (int i = 0; i < width * height - 1; i++) {
if (pixels[i] != pixels[i + 1]){
bordo[i] = 1;
}else{
bordo[i] = 0;
}
}
updatePixels();
E' importante capire come Processing tratta le informazioni contenute nel file: attraverso l'istruzione loadPixels(), i dati relativi ai pixel dell'immagine vengono caricati nell'array Pixels[], i cui elementi contengono il valore del colore di ogni singolo pixel.

L'immagine viene "affettata", nel senso della larghezza, in linee spesse 1px, quindi tutte le linee vengono poste una dietro l'altra, a formarne una unica. Se, ad esempio, avessimo un'immagine composta da 5x3 pixel, questa verrebbe trattata così:


Nel nostro caso, avendo usato la funzione threshold, i valori memorizzati saranno solo due: quello del bianco e quello del nero.

Adesso vogliamo semplificare il vettore in modo da avere degli "1" lungo i bordi e "0" in tutto gli altri punti. Per farlo scansioniamo il vettore Pixels[] attraverso un ciclio "for" e, quando troviamo due valori diversi per due pixel consecutivi, cosa che indica la presenza del bordo, memorizziamo nell'elemento corrispondente dell'array bordo[] il valore "1", mentre nell'altro caso, quando i due valori sono uguali, memorizziamo "0". Chiudiamo questo blocco con updatePixels(), consigliato anche se non strettamente necessario.
c = loadImage("DSC05842.JPG");
image(c, 0, 0, width, height);
loadPixels();
for (int i = 0; i < width * height - 1; i++) {
if (bordo[i] == 1){
x = i%width;
y = i/width;
stroke(pixels[i]);
beginShape();
vertex(x, y); // first point
bezierVertex( x + random(25), y + random(25), x + random(25), y + random(25), x + random(25), y + random(25));
endShape();
}
}

Ora conosciamo dove sono i bordi ma non abbiamo più i valori relativi ai colori di questi bordi, avendo applicato la funzione "filter(THRESHOLD, 0.4)".

Per questo carichiamo di nuovo la stessa immagine una seconda volta. Ora scansioniamo contemporaneamente gli array bordo[] e Pixels[] con un ciclo "for". Quando siamo sul bordo, prendiamo il colore del pixel corrispondente e ci tracciamo una curva attraverso la funzione "bezierVertex", assegnando dei valori random ai parametri che essa richiede, in modo da disegnare delle piccole curve. Ovviamente si può giocare con questi parametri per cambiare l'effetto risultante.

Il punto di partenza della curva sarà lungo il bordo e dobbiamo ricavare le sue coordinate, cioè dobbiamo fare il passaggio inverso a quello della figura qui sopra.

Come al solito, il punto origine del piano (0, 0) si trova nell'angolo in alto a sinistra, anche in Processing.

Guardando la figura qui sotto, supponiamo che nell'elemento 8 dell'array bordo[] ci sia memorizzato il valore "1". La posizione di questo punto nel piano è data dalle due coordinate (x, y), così ricavate: la x è il resto della divisione intera di 8 per 5 (la larghezza dell'immagine), quindi 3, mentre la y è data dalla parte intera della divisione di 8 per 5, quindi 1. Allora le coordinate sono (3, 1).

Siccome disegnamo le curve di Bezier nella sezione "draw()", questo calcolo verrà ripetuto ciclicamente, dando la sensazione di un movimento di questa specie di pelliccia.

Download dello sketch



Download the sketch

One of the most interesting features of Processing are the built-in functions for image processing.
To use them, you can see the internal help (Help-> Reference).

I wanted to create an effect acting on the edges of the subject of a picture, then I have a choice in which the board was sufficiently defined:


First you must load the image in your project via the Sketch menu -> Add Files..., this creates a folder "data" in which the image is copied to be used.

At this point we identify the edge of the subject. The piece of code that interests us is the following (to be included in the block "draw ()"):
b = loadImage("DSC05842.JPG");
image(b, 0, 0, width, height);
filter(THRESHOLD, 0.4);

We apply a filter to the image, through a call, "filter (THRESHOLD, 0.4), to the filter function, with the first parameter set to" Threshold ", which identifies precisely the contours of objects in the picture, turning the image into black and white. Playing a little with the second parameter (ranging from 0 to 1, default 0.5) we can improve the yield for our photography. The result is this:

loadPixels();
for (int i = 0; i < width * height - 1; i++) {
if (pixels[i] != pixels[i + 1]){
bordo[i] = 1;
}else{
bordo[i] = 0;
}
}
updatePixels();
It is important to understand how Processing uses the information contained in the file: through the loadPixels() function, the image pixel data are loaded into the array pixels[], elements of which contain the color value of every pixel.
The image is "sliced​​" in the sense of the width, in thick lines of 1px, and then all the lines are placed one after the other, forming a single array. If, for example, we had a picture made ​​up of 5x3 pixels, this would be treated like this:


We have used the threshold function, so the values in the array ​​will be only two: one for the white and one for the black.

Now we want to simplify the array in order to have the "1" along the edges and "0" in all other points. To do this, we scan the array Pixels[] through a "for" loop, and when we find two different values ​​for two consecutive pixels, which indicates the presence of the edge, we store in the corresponding array bordo[] the value "1". While in the other case, when the two values ​​are equal, store "0". We close this block with updatePixels(), recommended but not necessary.
c = loadImage("DSC05842.JPG");
image(c, 0, 0, width, height);
loadPixels();
for (int i = 0; i < width * height - 1; i++) {
if (bordo[i] == 1){
x = i%width;
y = i/width;
stroke(pixels[i]);
beginShape();
vertex(x, y); // first point
bezierVertex( x + random(25), y + random(25), x + random(25), y + random(25), x + random(25), y + random(25));
endShape();
}
}

Now we know where are the edges but we do not have values ​​for the colors of these edges, because we used the "filter (THRESHOLD, 0.4).

So we load again the same image a second time. Now we scan in the same time arraies bordo[] and Pixels[] by a "for" loop. When we're on the edge, we color the corresponding pixel, and we draw a curve through the "bezierVertex", assigning random values ​​to the parameters it requires, in order to draw small curves. Of course you can play with these parameters to change the resulting effect.

The starting point of the curve is along the edge and we will derive its coordinates, ie we have to apply the opposite function of the picture above.

As usual, the origin point of the plan (0, 0) is located in the upper left corner of the image, even in Processing.

Looking at the figure below, suppose that in the element 8 of the array bordo[] is stored the value "1". The location of this point in the plane is given by two coordinates (x, y), so obtained: x is the remainder of the integer division of 8 by 5 (the width of the image), then 3, and y is given by the integer part of the integer division of 8 by 5, then 1. So the coordinates are (3, 1).

As we draw Bezier curves in the "draw()" section, this calculation will be repeated periodically, giving the feeling of a movement of this kind of fur.

Download the sketch

Nessun commento:

Posta un commento