Array di pixel: loadPixels() e updatePixels()

Avevo parlato di pixel in uno dei primissimi post su Processing in questo blog. Grazie alle competenze che abbiamo acquisito nelle ultime settimane, possiamo fare un ulteriore passo in avanti.

Le funzioni che abbiamo usato fino a oggi ci hanno permesso di disegnare linee e forme sullo schermo o, come visto di recente, di mostrare un’immagine. Queste funzioni che, all’apparenza, sembrano eseguire operazioni molto semplici, in realtà nascondono un principio molto complesso: stabilire se ciascun pixel sullo schermo deve essere accesso o spento e, se acceso, il colore che deve rappresentare.

Array di pixel

Siamo abituati a pensare ai pixel come ad una griglia sullo schermo; quindi ad un array bidimensionale.

In Processing, però, il valore di ciascuno di essi viene salvato in un classico array monodimensionale.

È possibile accedere a queste informazioni e modificarle a nostro piacimento? Ovviamente si, utilizzando le funzioni loadPixels()updatePixels().

loadPixels() e updatePixels()

Con la prima funzione stiamo dicendo a Processing che intendiamo lavorare sull’array di pixel e che, quindi, deve caricarlo in una variabile. updatePixels(), invece, la usiamo quando abbiamo apportato tutte le modifiche e vogliamo che il programma aggiorni le informazioni sullo schermo.

Facciamo un esempio pratico:

/*
 * Array di pixel: loadPixels() e updatePixels()
 * Federico Pepe, 26.03.2017
 * http://blog.federicopepe.com/processing
 */

void setup() {
  size(500, 500);
  loadPixels();  
 
  for (int i = 0; i < pixels.length; i++) {
    float rand = random(255);
    color c = color(0, 0, rand);
    pixels[i] = c;
  }
  
  updatePixels();
}

In questo programma carichiamo tutti i valori di una finestra di 500x500 pixel. Con il ciclo for partiamo dal primo valore presente nel nostro array e arriviamo fino all'ultimo, identificato, per comodità, dalla funzione pixel.length e assegniamo a ciascuno un colore casuale float rand = random(255); nella scala dei blu color c = color(0, 0, rand);.

Assegniamo il nuovo colore al pixel pixels[i] = c; e, una volta usciti dal loop, chiediamo a Processing di aggiornare e mostrare tutte le modifiche che abbiamo effettuato updatePixels();.

Il risultato sarà il seguente:

Pixel Array Blu

Modificare un pixel conoscendone la posizione x e y

A questo punto, immagino, potrà esservi sorta spontaneamente una domanda: è possibile accedere a un determinato pixel conoscendone la posizione x e y all'interno della finestra come siamo stati abituati a fare fino a ora?

Riprendendo l'immagine della griglia più sopra, proviamo a capire come accedere al pixel 19: la sua posizione è x = 5, y = 2. A questo punto è importante ricordare che, per questi valori, partiamo a contare da 0 mentre, per la larghezza della finestra, da 1.

Ora dobbiamo sommare il valore di x al valore di y moltiplicato alla larghezza.

La formula è, dunque: x + (y * width)

5 + (2 * 7) = 19

I conti tornano!

Proviamo a modificare il codice di prima come segue:

/*
 * Array di pixel: loadPixels() e updatePixels()
 * Federico Pepe, 26.03.2017
 * http://blog.federicopepe.com/processing
 */

void setup() {
  size(500, 500);
  loadPixels();  
  color c;
  for (int x = 0; x < width; x++) {
    for (int y = 0; y < height; y++) {
      float rand = random(255);
      int pos = x + y * width;
      if(x % 2 == 0) {
        c = color(0, 0, rand);
      } else {
        c = color(rand, 0, 0);
      }
      pixels[pos] = c;
    }
  }
  
  updatePixels();
}

Zoomando l'immagine noterete che una riga è rossa e una riga è, invece, blu. Il prossimo passo sarà applicare quello che abbiamo appena imparato su un'immagine. Provate a pensare a tutte le operazioni che potremmo compiere andando a modificare individualmente ogni singolo pixel.

Modificare le immagini: tint(), filter()

Proseguiamo l’approfondimento su Processing e immagini. Nell’era di Instagram siamo abituati a cambiare l’aspetto delle nostre foto prima di condividerle con gli altri. Le funzioni tint()filter() – già incluse all’interno del linguaggio – ci consentono di alterare le immagini a nostro piacimento.

La funzione tint()

Questa funzione consente di alterare il colore generale delle foto. L’effetto non è permanente tanto che invocando la funzione noTint() è possibile ritornare all’originale.

I parametri accettati vanno da uno a quattro come specificato nel reference. Prendendo quest’immagine di partenza –chi non ama i gatti? – facciamo qualche esempio:

Immagine di partenza, presa da Pixabay (CC)
Passiamo alla funzione un solo parametro per applicare un filtro “grigio”:

/*
 * Immagini: tint(), filter() e masking
 * Federico Pepe, 19.03.2017
 * http://blog.federicopepe.com/processing
*/

PImage immagine;

void setup() {
  size(640, 535);
  immagine = loadImage("cat.jpg");
  tint(100);
  image(immagine, 0, 0);
}

void draw() {
 
}

Passiamo a tint() tre parametri: tint(255, 255, 50);. È possibile usare tre valori RGB che HSB in base alla funzione colorMode() precedentemente impostata.

Processing use tint()

Se avete un background in web design, sappiate che Processing accetta anche valori esadecimali tint(#FF55EE);

Processing tint hexadecimal

È possibile anche sfruttare il parametro della trasparenza alpha.

tint(#FF55EE, 70);
image(immagine, 0, 0);
tint(255, 255, 50, 50);
image(immagine, 0, 0);

Questo codice darà il seguente risultato:

Processing tint e trasparenza

Aggiungere un filtro con la funzione filter()

Oltre alla funzione tint(), Processing ha al suo interno la funzione filter() che, come i comuni programmi di grafica come Adobe Photoshop o GIMP, permette di applicare dei filtri alle foto.

I parametri sono otto e devono essere sempre indicati in maiuscolo: THRESHOLD, GRAY, INVERT, POSTERIZE, BLUR, OPAQUE, ERODE e DILATE. Alcuni di essi accettano un secondo parametro mentre altri no.

Mentre la funzione tint() doveva essere dichiarata prima di mostrare l’immagine, la funzione filter() è inserita successivamente, come indicato nel seguente esempio:

/*
 * Immagini: tint(), filter() e masking
 * Federico Pepe, 19.03.2017
 * http://blog.federicopepe.com/processing
*/

PImage immagine;

void setup() {
  size(640, 535);
  immagine = loadImage("cat.jpg");
  tint(100);
  image(immagine, 0, 0);
}

void draw() {
 
}

THRESHOLD

Converte i pixel di un’immagine in bianco o nero in base alla soglia impostata che può assumere un valore compreso tra 0 e 1. Se non viene indicato, il valore di default è 0.5.

BLUR

Applica un filtro di sfocatura gaussiana. Il secondo parametro indica il raggio della sfocatura.

POSTERIZE

Limita ciascun canale dell’immagine al numero di colori passati come parametro. I valori accettati vanno da 2 a 255.

GRAY

Converte i colori dell’immagine nel loro valore corrispondente in scala di grigi.

OPAQUE

Imposta il canale trasparenza (alpha) opaco.

INVERT

Inverte il valore di ciascun pixel.

ERODE e DILATE

Il primo riduce le aree illuminate mentre il secondo le aumenta.

Le funzioni che abbiamo visto oggi sono solo l’inizio; prossimamente vedremo come creare i nostri filtri personalizzati andando a lavorare sui singoli pixel.

Utilizzare immagini in Processing: PImage, loadimage() e image()

In tutti gli sketch che abbiamo creato fino a oggi abbiamo sempre utilizzato Processing per generare forme sullo schermo. Con questo post, invece, cominceremo ad ampliare i nostri orizzonti e inizieremo a utilizzare nei nostri programmi immagini esterne.

A differenza dei programmi di editing più comuni – penso per esempio a Photoshop o Pixelmator – con Processing possiamo lavorare in modo più preciso e dettagliato su ogni singola foto andando a regolare, a nostro piacimento, ogni pixel.

Le immagini sono dati

Le immagini digitali non sono altro che un insieme di dati, ovvero numeri. Per cercare di spiegarlo nel modo più semplice possibile, ciascun dato rappresenta i valori di rosso, verde e blu (RGB) di un punto preciso all’interno di una griglia.

Di fatto possiamo immaginare ogni foto come una griglia di pixel dove ciascun pixel avrà diversi valori di rosso, verde e blu. Con la programmazione e un po’ di fantasia possiamo avere accesso a tutti questi dati e usarli a nostro piacimento.

PImage, loadImage() e image()

Abbiamo già parlato di tipi di dati e classi. Processing ha al suo interno la classe PImage la cui funzione principale è quella di permetterci di caricare e visualizzare un’immagine.

La funzione loadImage(), che accetta un parametro di tipo String, carica il file all’interno della memoria. Nell’esempio riportato in seguito, il parametro passato alla funzione è il nome del file comprensivo di estensione. La funzione andrà a cercare se quel file è presente all’interno della sottocartella data presente all’interno del nostro sketch.

Per chi non lo ricordasse, in questo post abbiamo già trattato come caricare file esterni all’interno del nostro sketch in Processing.

Mettiamo tutto insieme:

/*
 * Utilizzare immagini in Processing: PImage, loadimage() e image()
 * Federico Pepe, 12.03.2017
 * http://blog.federicopepe.com/processing
*/

PImage immagine;

void setup() {
  size(500, 500);
  immagine = loadImage("logo_federicopepe.png");
}

void draw() {
  image(immagine, 0, 0);
}

Ho creato una variabile di tipo PImage chiamata immagine; attraverso la funzione loadImage() carico il file chiamato logo_federicopepe.png che, in precedenza, avevo già spostato all’interno della cartella data e, infine, con la funzione image() visualizzo l’immagine facendo in modo che il bordo in alto a sinistra corrisponda alle coordinate 0, 0.

Ecco il risultato:

loadImage(), image() e PImage

L’immagine viene visualizzata correttamente perché il file originale è di 500 x 500 pixel. Per completezza condivido anche lo screenshot della cartella dello sketch.

La cartella dello sketch che contiene l'immagine

Processing accetta i seguenti tipi di file: GIF, JPG, TGA PNG.

Un paio di precisazioni

Abbiamo detto che PImage è una classe. Perché non abbiamo mai chiamato un constructor per inizializzarla? La funzione loadImage() in questo caso ha la funzione del constructor restituendo una nuova istanza dell’oggetto PImage generata dal file che carichiamo.

Se quest’ultima frase vi sembra arabo, consiglio di ripassare i post dedicati alla programmazione ad oggetti:

Come illustrato nel reference la funzione image() richiede un minimo di tre parametri: immagine da caricare, posizione x e y. È possibile passare alla funzione due parametri ulteriori per specificare la larghezza e l’altezza dell’immagine: un metodo comodo e veloce per ridimensionare le foto:

/*
 * Utilizzare immagini in Processing: PImage, loadimage() e image()
 * Federico Pepe, 12.03.2017
 * http://blog.federicopepe.com/processing
*/

PImage immagine;

void setup() {
  size(500, 500);
  immagine = loadImage("logo_federicopepe.png");
}

void draw() {
  // Utilizzo due parametri aggiuntivi per ridimensionare la foto
  image(immagine, 0, 0, 100, 100);
}

Immagine ridimensionata

Processing e Ableton: sincronizzare musica e visual via MIDI

Oggi sfrutteremo Processing per creare dei semplici visual sincronizzati con la musica attraverso il protocollo MIDI. Il software musicale che userò per fare gli esempi è Ableton Live 9 ma è possibile usare qualsiasi Digital Audio Workstation in grado di inviare messaggi MIDI.

Lo schema delle connessioni, dunque, sarà: Ableton Live gestirà la musica e invierà, attraverso dei messaggi MIDI, delle informazioni che Processing acquisirà in input per generare i visual.

MIDI e the MidiBus

Perché ho deciso di sfruttare il protocollo MIDI? Le ragioni sono molto semplici:

  1. è uno standard per le applicazioni musicali
  2. è un protocollo flessibile
  3. è semplice creare, inviare e monitorare i messaggi MIDI.
  4. rende molto semplice la sincronizzazione

Per questioni di spazio, non mi dilungherò in questo post sulla spiegazione di come funziona questo protocollo; trattandosi di uno standard sviluppato negli anni ’80, ci sono numerose risorse on-line che vi permettono di approfondire.

Libreria: the MidiBus

Anche se Java supporta nativamente il MIDI attraverso il pacchetto java.sound.midi, per lavorare con questi tipi di messaggi in Processing consiglio di installare una libreria.

Processing Contribution Manager MIDI Bus

Aprite Processing, andate nel menu Tool > Add new tool. Nel pannello Libraries digitate MIDI, selezionate The Midi Bus e cliccate sul pulsante Install in basso a destra.

macOS: IAC Driver

Di norma per inviare e ricevere messaggi MIDI è necessario dotarsi di una scheda e dei relativi cablaggi. Nel nostro caso, però, dovendo inviare e ricevere messaggi tra due applicazioni possiamo sfruttare lo IAC Driver: una scheda virtuale che ci permette di gestire tutte le connessioni internamente al computer.

Nota: Per Windows esistono delle applicazioni da installare che hanno la stessa funzione del driver IAC. Con una veloce ricerca su Google ho trovato LoopBe1 e MIDI Yoke che non ho provato personalmente ma sono consigliate su diversi forum.

Per configurare lo IAC Driver su macOS aprite Applicazioni > Utility > Configurazione MIDI Audio. Cliccate su Finestra > Mostra Studio MIDI ⌘2

Studio MIDI macOS IAC Driver

Fate doppio click su IAC Driver e mettete la spunta su Il dispositivo è acceso.

mac OS accendere IAC Driver

Ora che abbiamo attivato il canale di comunicazione interno per inviare e ricevere messaggi MIDI, possiamo procedere con Processing.

Ricezione messaggi MIDI in Processing

Apriamo un nuovo sketch e verifichiamo subito che Processing veda lo IAC Driver.

/*
 * Processing e Ableton: sincronizzare musica e visual via MIDI
 * Federico Pepe, 05.03.2017
 * http://blog.federicopepe.com/processing
*/

import themidibus.*;

void setup() {
  size(100, 100);
  background(0);

  MidiBus.list(); 
}

void draw() {
  
}

Con queste poche righe di codice abbiamo importato la libreria all’interno del nostro programma e attraverso il comando Midibus.list() possiamo verificare nella console tutte le periferiche MIDI attive.

Questo è il risultato che ottengo lanciando il programma sul mio Mac. Ovviamente potrebbe cambiare sul vostro; l’importante è che sia presente IAC Bus.

Available MIDI Devices:
----------Input----------
[0] "IAC Bus 1"
[1] "Real Time Sequencer"
----------Output----------
[0] "Gervill"
[1] "IAC Bus 1"
[2] "Real Time Sequencer"

Progetto Ableton Live: invio del messaggio

Apriamo Ableton Live e creiamo un nuovo progetto. Apriamo le preferenze e verifichiamo che il driver IAC sia selezionato come scheda sia per l’input che per l’output.

Ableton Live 9 MIDI Preferences

Nella prima traccia MIDI nella parte di input/output selezioniamo MIDI ToIAC Driver. In questo modo tutti i messaggi che saranno presenti su questa traccia saranno inviati come output al driver e, di conseguenza, saranno ricevuti da Processing.

Ableton MIDI Out Tracks

Creiamo una Clip MIDI nel primo slot contenente un C3 lungo 1/4. Assicuriamoci che la clip sia il loop quando premiamo il tasto play su di essa.

MIDI Clip

Ricezione del messaggio

Torniamo su Processing e modifichiamo il nostro programma come segue:

/*
 * Processing e Ableton: sincronizzare musica e visual via MIDI
 * Federico Pepe, 05.03.2017
 * http://blog.federicopepe.com/processing
*/

import themidibus.*;

MidiBus myBus;

void setup() {
  size(100, 100);
  background(0);

  MidiBus.list(); 
  
  myBus = new MidiBus(this, 0, 1);
}

void draw() {
  
}

void noteOn(int channel, int pitch, int velocity) {
  println();
  println("Note On:" + " Channel: "+channel + " Pitch: "+pitch + " Velocity: "+velocity);
  println("--------");
}

Rispetto al codice scritto in precedenza, abbiamo aggiunto e inizializzato un oggetto di tipo MidiBus: myBus = new MidiBus(this, 0, 1);. In questa fase è fondamentale inserire i parametri corretti: il primo è il parent a cui la libreria è collegata, il secondo è l’input e l’ultimo l’output. I dati sono facilmente individuabili grazie al comando .list() dato in precedenza.

Abbiamo, infine, aggiunto una funzione che è presente all’interno della libreria: noteOn() e che restituisce i valori MIDI: canale, pitch e velocity di tutte le note ricevute.

Verifichiamo il funzionamento

Facciamo partire la nostra clip su Ableton Live e avviamo il nostro sketch su Processing. Se in console appare un messaggio come quello qui di seguito, siamo riusciti nel nostro intento:

Available MIDI Devices:
----------Input----------
[0] "IAC Bus 1"
[1] "Real Time Sequencer"
----------Output----------
[0] "Gervill"
[1] "IAC Bus 1"
[2] "Real Time Sequencer"

Note On: Channel: 0 Pitch: 60 Velocity: 127
--------

Processing sta ricevendo tramite IAC Driver un messaggio Note On con i seguenti parametri:

  • Canale: 0
  • Pitch: 60 (che corrisponde al C3)
  • Velocity: 127

Adesso, se andiamo a modificare la clip su Ableton inserendo nuovi messaggi, possiamo verificare che anche questi siano ricevuti da Processing.

Creare i visual

A questo punto non ci resta che sviluppare in Processing i nostri visual sfruttando i parametri MIDI inviati da Ableton. Nell’esempio molto semplice che trovate di seguito, viene disegnato un quadrato al centro dello schermo la cui dimensione dipende dal valore di pitch ricevuto via MIDI. In aggiunta a quanto visto in questo post, sfrutto la funzione noteOff() per impostare la grandezza del quadrato a 0. Di certo non è il metodo più elegante ma di sicuro funziona.

/*
 * Processing e Ableton: sincronizzare musica e visual via MIDI
 * Federico Pepe, 05.03.2017
 * http://blog.federicopepe.com/processing
*/

import themidibus.*;

MidiBus myBus;

int size;

void setup() {
  size(400, 400);
  background(0);

  MidiBus.list(); 
  
  myBus = new MidiBus(this, 0, 1);
}

void draw() {
  background(0);
  rectMode(CENTER);
  rect(width/2, height/2, size, size);
}

void noteOn(int channel, int pitch, int velocity) {
  println();
  println("Note On:" + " Channel: "+channel + " Pitch: "+pitch + " Velocity: "+velocity);
  println("--------");
  size = pitch;
}

void noteOff(int channel, int pitch, int velocity) {
  size = 0;
}

Ed ecco il risultato: