Vai al contenuto

Processare le immagini in Processing

In questo post vedremo come unire le nozioni che abbiamo appreso relativamente al caricamento e all' utilizzo delle immagini in Processing e alle possibilità creative che abbiamo a disposizione lavorando sui singoli pixel per imparare a processore le immagini a nostro piacimento.

Riprendiamo l'immagine del gatto che abbiamo già usato in precedenza e carichiamola all'interno di un nuovo sketch. Invece di utilizzare la funzione image() per mostrarla nella finestra, questa volta andremo a caricare tutti i pixel che compongono l'immagine.

/*
 * Processare le immagini in Processing
 * Federico Pepe, 29.04.2017
 * http://blog.federicopepe.com/processing
 */

PImage img;

void setup() {
  size(640, 536);
  img = loadImage("cat-300572_640.jpg");
}

void draw() {
  // Carichiamo i pixel della finestra
  loadPixels();
  // Carichiamo i pixel dell'immagine
  img.loadPixels();
  for(int y = 0; y < height; y++) {
    for(int x = 0; x < width; x++) {
      int pos = x + y * width;

      float r = red(img.pixels[pos]);
      float g = green(img.pixels[pos]);
      float b = blue(img.pixels[pos]);

      pixels[pos] = color(r, g, b);
    }
  }
  updatePixels();
}

Analizziamo velocemente il codice qui sopra concentrandoci, in particolare, sulle cose che non abbiamo mai visto prima: come indicato nei commenti, oltre a caricare i pixel della finestra dobbiamo caricare anche quelli relativi all'immagine. Per farlo utilizziamo img.loadPixels(); dove img fa riferimento al nome della variabile che abbiamo dichiarato all'inizio del programma.

Con due loop for andiamo a caricare ogni singolo pixel e, utilizzando le funzioni red()green()blue() otteniamo i valori R, G e B. Attraverso pixels[pos] = color(r, g, b); assegniamo i valori al pixel sullo schermo.

Il nostro programma, in pratica, lavora come segue: che valori di rossoverdeblu ha il pixel nell'immagine che ha coordinate x = 0, y = 0? Una volta ottenuti, applica quei valori al pixel con posizione x = 0 e y = 0 della finestra. Dopodiché, il ciclo for prosegue e analizzerà il pixel con coordinate x = 1 e y = 0 e avanti così.

Con l'updatePixel() finale aggiorniamo tutti i valori presenti nell'array della finestra. Il risultato finale sarà, dunque, vedere visualizzata sullo schermo l'immagine.

La domanda che sorge spontanea è: perché dovrei scrivere tutte queste righe di codice quando avrei potuto usare la funzione image() e risparmiare un sacco di fatica? La risposta è semplice: avendo accesso ai dati grezzi dell'immagine possiamo modificarli a nostro piacimento.

Proviamo, ad esempio, ad aggiungere la riga in grassetto al nostro codice:

r = constrain(r, 0, 100);

pixels[pos] = color(r, g, b);

Il risultato sarà che il valore del rosso non potrà avere un valore compreso tra 0 e 255 come previsto normalmente ma sarà limitato a valori compresi tra 0, 100 grazie alla funzione constrain.

Processare le immagini in Processing

Siamo riusciti a creare il nostro primo filtro personalizzato per le immagini. Ora possiamo dare sfogo alla nostra fantasia.

Facciamo un altro esperimento: rimpiazziamo l'ultima riga di codice che abbiamo aggiunto con quella seguente:

r = map(mouseX, 0, width, 0, 255);

Il rosso ora è controllato dalla posizione x del mouse.

Usare un solo ciclo for

Prima di concludere questo post ci tengo a fare una precisazione: volendo è possibile utilizzare un solo ciclo for per ottenere lo stesso effetto riducendo, così, le righe di codice del nostro programma:

for(int i = 0; i < pixels.length; i++)

Se decidiamo di intraprendere questa strada dobbiamo eliminare la variabile pos e sostituirla con i:

float r = red(img.pixels[i]);

Come avevamo discusso nel post relativo all'array di pixel la differenza tra i due approcci dipende se consideriamo l'immagine un array monodimensionale o bidimensionale.