Programmazione di applicazioni multimediali in Processing Progettazione e produzione multimediale - 2008 Walter Nunziati - [email protected] Sommario Introduzione al linguaggio e all’ambiente di sviluppo Processing Elaborazione e analisi di immagini e video in Processing Esercitazioni pratiche Introduzione La programmazione è diventata un metodo per creare contenuti multimediali. In particolare, le Rich Internet Application (RIA) prevedono una complessa interazione con l’utente, che richiede l’uso di strumenti di programmazione al fianco dell’XHTML. Vari ambienti di programmazione sono oggi usati dai progettisti di contenuti a fianco dei tradizionali strumenti di authoring. Processing Processing is an open source programming language and environment for people who want to program images, animation, and sound. It is used by students, artists, designers, architects, researchers, and hobbyists for learning, prototyping, and production. It is created to teach fundamentals of computer programming within a visual context and to serve as a software sketchbook and professional production tool. Processing is developed by artists and designers as an alternative to proprietary software tools in the same domain. Cosa è Processing in pratica? Un linguaggio di programmazione derivato da Java Un insieme di librerie dedicate in particolare allo sviluppo di applicazioni multimediali (gestione suono, immagini, video, primitive grafiche, ecc.) Un ambiente di sviluppo semplice e intuitivo Riferimenti Sito web: www.processing.org/learning/index.html www.processing.org/reference/index.html Libri: Processing: A Programming Handbook for Visual Designers and Artists Casey Reas and Ben Fry, MIT Press 2007 Processing: Creative Coding and Computational Art (Foundation) Ira Greenberg Quick tour Esempio: helloworld Why processing? Open-source, gratuito e multi piattaforma Ampiamente supportato Non richiede un ambiente di run-time esterno (es. il browser) Nasconde molti “aspetti noiosi” del processo di sviluppo Produce eseguibili multi piattaforma e applet web Struttura di un programma processing I programmi processing sono composti da moduli chiamati Sketch Ogni modulo è essenzialmente un file di testo, e contiene istruzioni e funzioni del programma Ogni programma ha un modulo principale, che viene lanciato al momento dell’esecuzione Ogni programma contiene (nel modulo principale) tipicamente le seguenti funzioni (in continuous programming mode): setup() - eseguita all’inizio del programma per le operazioni di inizializzazione draw() - eseguita continuamente in loop (è il ciclo principale del programma) Tale comportamento può essere alterato attraverso le funzioni loop(), noloop(), e redraw(). Esempio: ContinuousModeExample1 Cenni di programmazione strutturata Sintassi Dichiarazione variabili Tipi di dato e scope delle variabili Istruzioni di assegnamento Costrutti di selezione e iterazione Dichiarazione e uso di funzioni Sintassi (base) Ogni riga che inizia con // è un commento Ogni blocco compreso tra /* e */ è un commento Ogni istruzione deve terminare con ; Le istruzioni sono case-sensitive Istruzioni Un programma è composto da una sequenza di istruzioni. Si hanno: Istruzioni di assegnamento Istruzioni di selezione Istruzioni di iterazione Variabili e istruzioni di assegnamento Usate per memorizzare dei valori Hanno un nome e un valore Case-sensitive Usate nomi descrittivi, e siate coerenti con il naming! String name = "Daniel"; // Declare and assign int number = 32; // Declare and assign int counter = 12; // Assign variable print(number); print(name); Tipi di dato di base int //Integer: 1, 2, 3, ... float //0.1, 2.747, ... char // “$”, “A”, stores one character. String //”Daniel”, stores a series of characters. boolean //true or false. // ARRAY (VETTORI): int[] vettore = new int[]; Nota: non tutti i linguaggi sono “tipizzati” Scope delle variabili Una variabile dichiarata all’interno di una funzione è visibile solo in quella funzione! Una variabile dichiarata all’esterno di tutte le funzioni, e visibile a tutto il programma (si dice GLOBALE) E’ buona norma non abusare delle variabili globali! Tipi non primitivi Accanto ai tipi primitivi che abbiamo visto all’inizio (int,char,float,ecc.), ne esistono molti altri definiti all’interno di varie librerie. Ad esempio, una variabile di tipo PImage è utilizzata in processing per memorizzare immagini L’uso dei tipi non primitivi segue (quasi) le stesse regole di dichiarazione dei tipi primitivi: PImage img; Array Un array (vettore) è un contenitore di oggetti dello stesso tipo, indicizzati da un numero intero. Il primo elemento ha indice 0. L’ultimo n-1, dove n è il numero di elementi dell’array Dichiarazione: int[] myArray = new int[10]; int[] myArray = {1919, 1940, 1975, 1976, 1990}; Un’array può essere inizializzato al momento della dichiarazione: int myArray = {100,200,400}; Si accede all’elemento i-esimo dell’array usando le parentesi quadre: int a = myArray[5]; Es (disegno di una forma): Funzioni Definizione di funzioni Le istruzioni vengono tipicamente raggruppate in funzioni, che eseguono in blocco una particolare sequenza di istruzioni ogni volta che vengono chiamate Una funzione si DEFINISCE con tiporitornato nomefunzione(par1,pa2,..) { // corpo della funzione } ES (dichiarazione): int addizione(int i1, int i2) { int risultato = i1 + i2; return risultato; } Chiamata di funzioni Una funzione precedentemente definita si richiama con: valore-ritornato = nome-funzione(par1,pa2,..); Es: int valoreSomma = addizione(v1,v2); Operatori logici && (and) || (or) ! (not) Es. if (a==true && i>10) { } Selezione - if/then/else // statements within the if condition are only // executed when if true if (i < 35) { line( 30, i, 80, i ); } else { line( 20, i, 90, i ); } // else is executed if condition is false Iterazione (for) // for() loop: repeat with the defined conditions // parameters: init, test, update for(int i=0; i<10; i++) { line(30, i, 80, i); } Iterazione (while) // while() loop: repeat as long as condition is true int i=0; while(i<10) { line(30, i, 80, i); i++; } Sistema di coordinate Processing uses a Cartesian coordinate system with the origin in the upper-left corner. If your program is 320 pixels wide and 240 pixels high, coordinate [0, 0] is the upper-left pixel and coordinate [320, 240] is in the lowerright. The last visible pixel in the lower-right corner of the screen is at position [319,239] Disegno di primitive grafiche //draws a point at x=10 and y=20 point(10, 20); // draws a line from x1, y1 to x2, y2 line(20, 20, 160, 120); // draws a rectangle: x1, y1, width, height rect(20, 20, 160, 120); // draws an ellipse: x, y, width, height ellipse(20, 20, 40, 40); // draws a four sided polygon x1, y1, x2, y2, x3, y3, x4, y4 quad(38, 31, 86, 20, 69, 63, 30, 76) Pennello e riempimento stroke(120); // gray stroke stroke(0, 0, 255); // blue stroke stroke (255, 0, 0, 122); // red transparent noStroke(); // no Stroke fill(100); fill(255, 0, 0); fill(200, 200, 200, 100); // grey transparent noFill(); Note: A fourth parameter for fill and stroke sets the transparency of the color value. Trasformazioni del sistema di riferimento Le funzioni translate(), rotate() e scale() permettono di applicare delle trasformazioni euclidee al sistema di riferimento (e quindi alle figure disegnate) rect(0, 0, 50, 50); translate(30, 20); fill(0); rect(0, 0, 50, 50); fill(102); rect(15, 10, 50, 50); Esempio: TranslateExample Push/Pop Matrix pushMatrix() e popMatrix() permettono di isolare le trasformazioni e raggrupparle in uno stack (matrix stack) In questo modo, è possibile memorizzare e richiamare lo stato del sistema di riferimento. Esempio: MatrixStackExample Esercizio - drawing shapes int i = 0; int windowSize = 400; void setup() { size(windowSize, windowSize); background(0); frameRate(10); } void draw() { i++; fill(255); float posX = width/2; float posY = height/2; float radius = random(0, 100); ellipse(posX, posY, radius, radius); } Esempio: Esercizio1 Scopi del primo esercizio Familiarizzare con le funzioni setup(), draw(), frameRate(), size(), background(), fill(), random(), ellipse(). Uso delle COSTANTI built-in (width, height) Usare il costrutto di selezione per controllare l’evoluzione delle forme disegnate. “Building on existing code” Esplorare le funzioni di disegno primitive grafiche. Image processing and analysis Processing: applicare algoritmi a un’immagine per ottenere degli effetti (aka photoshopping) Analysis: applicare algoritmi a un’immagine per capirne il contenuto Caricamento di un’immagine Le immagini sono memorizzate all’interno di variabili di tipo PImage (tipo non primitivo) Per aggiungere un’immagine allo sketch, basta trascinarla sulla finestra di editing Per caricare un’immagine nel programma, si usa la funzione loadImage(): PImage img; img = loadImage(“nomefile.jpg”); Display di un’immagine Per mostrare un’immagine a schermo, si usa la funzione image(). Es: image(img, 0, 0); Mostra l’immagine contenuta in img a partire dall’angolo in alto a sinistra. • tint() colora l’immagine del colore specificato. Elaborazione di immagini Le funzioni get() e set() possono essere usate per uno sporadico accesso ai pixel Per un uso più intensivo, si usano le funzioni loadPixels() e updatePixels() loadPixels() ha l’effetto di mettere tutti i pixel dell’immagine in un array di dimensione ROWS*COLS, di nome pixels[]. updatePixels() “restituisce” i pixel all’immagine Struttura di elaborazione tipica // carica l’immagine PImage myImage = loadImage("topanga.jpg"); image(myImage, 0, 0); //elabora i pixel loadPixels(); for (int i = 0; i < width*height; i++) { pixels[i] = color(200,20,30); } updatePixels(); Esempio: ImageExample La funzione filter filter(MODE) (o filter(MODE, level)) applica un algoritmo all’immagine Es. filter(THRESHOLD, 0.5) converte l’immagine in bianco e nero Filtri disponibili: THRESHOLD, GRAY, INVERT, POSTERIZE, BLUR, OPAQUE, ERODE, DILATE La funzione blend Sovrappone una regione di una immagine su un’altra (o su se stessa) blend(x, y, width, height, dx, dy, dwidth, dheight, MODE) blend(srcImg, x, y, width, height, dx, dy, dwidth, dheight, MODE) MODE: BLEND, ADD, SUBTRACT, LIGHTEST, DARKEST.. Eventi Sono una importante funzionalità messa a disposizione dell’ambiente processing Un evento è una funzione che viene chiamata al verificarsi di certe condizioni Es. mousePressed() è chiamata ogni volta che viene premuto un bottone del mouse void setup() { size(200,200); fill(0); } void draw() {} void mousePressed() { rect(mouseX,mouseY,20,20); } Esempio: MousePressedExample Eventi disponibili (input) mouseDragged() mouseMoved() mouseReleased() mousePressed() keyReleased() keyPressed() Variabili predefinite mouseX, mouseY: coordinate del mouse nel frame corrente pmouseX, pmouseY: coordinate del mouse nel frame precedente mouseButton: LEFT, RIGHT, CENTER mousePressed: vera se il mouse è correntemente premuto. NB: si chiama come la funzione mousePressed()!!! Analoghe variabili sono disponibili per gli input da tastiera Uso dei font I font devono preventivamente essere “creati” usando l’apposito strumento dal menu Tools/Create fonts.. Una volta creato, il font viene messo nella cartella “data” dello sketch Per utilizzarlo, si usa il tipo PFont: PFont font = loadFont(“Courier-48.vlw”); Video Le funzioni per l’uso dei video sono in processing.video.*; I video vengono gestiti attraverso il tipo di variabile Movie I video devono essere in formato mov. Non sono molti i codec supportati (mpeg4 ok, ffmpeg può essere usato per la conversione) Ad ogni frame viene chiamato l’evento movieEvent() I singoli frame possono essere trattati e elaborati come immagini. Riproduzione di un filmato: import processing.video.*; Movie myMovie; void setup() { size(360,240); myMovie = new Movie(this, "ronaldinho.avi"); myMovie.loop(); } void draw() { image(myMovie, 0,0); myMovie.read(); } Esempio: VideoExample1 Elaborazione dei frame void draw() { // Read the new frame from the movie video.read(); // make its pixels array available video.loadPixels(); for (int i = 0; i < pixels.length; i++) { color currColor = video.pixels[i]; // Render the grayscale image to the screen video.pixels[i] = color(brightness(currColor)); } video.updatePixels(); } Esempio: VideoExample2 Video from camera Sono supportate le telecamere USB e Firewire, ma il supporto varia a seconda dei sistemi operativi (viene utilizzato QuickTime per l’acquisizione, linux è MOLTO penalizzato) La classe utilizzata è Capture Capture.list() mostra i dispositivi di input video disponibili. La gestione è simile a quella dei video, seppure (ovviamente) con meno controlli. movieEvent e captureEvent Chiamati ad ogni frame decodificato void movieEvent(Movie m) { m.read(); } void captureEvent(Capture myCapture) { myCapture.read(); } Classi e oggetti Non tutte le funzionalità e le librerie di Processing possono essere utilizzate tramite l’approccio “a funzioni”. Alcune parti sono implementate dal linguaggio secondo un modello “a oggetti”, con cui a volte è necessario avere a che fare. In generale, tutto ciò che sta fuori da Processing-core deve essere usato “a oggetti”. Classe Una classe definisce un tipo di dato non primitivo, sia in termine dei dati che contiene, che delle operazioni che si possono svolgere su di esso. Es. PImage è una classe, che serve per gestire le immagini Variabili e oggetti Quando si dichiara una variabile di un tipo di dato non primitivo, si dice che si definisce un oggetto di quella classe. Es. PImage img; Dichiara un’oggetto di classe (tipo) PImage. L’operatore new Per creare (istanziare) un’oggetto, ci sono di solito due modi: Usare una funzione che “fa il lavoro” per noi. Es. PImage img = loadImage(“path”); Usare l’operatore new: PImage img = new PImage(); Metodi Gli oggetti hanno tipicamente delle funzioni che possono essere invocate su di essi. Tali funzioni si chiamano metodi. Es: // dichiarazione e creazione di una variabile di classe Movie Movie movie = new Movie(this, “videofile”); // Chiamata del metodo play(); movie.play(); Librerie Built-In Video Networking Serial Communication Importing XML, SVG Exporting PDF, DXF, etc. External Contributions Sound: Ess, Sonia Computer Vision: JMyron, ReacTIVision, BlobDetection Interface: proCONTROLL, Interfascia ... Installazione di librerie Le librerie aggiuntive devono essere scaricate a parte Tipicamente, è sufficiente copiare il file .jar fornito all’interno della cartella libraries contenuta nell’installazione del processing In alcuni casi vengono forniti anche dei componenti nativi (.dll, .so) che devono essere aggiunte al library path di java Uso di librerie L’istruzione import all’inizio di un modulo permette di utilizzare le librerie definite nella clausola import in quel modulo. Es. per importare la libreria del video: import processing.video.*; Ess library (sound) Ess r2 is a sound library that allows sound sample data to be loaded or streamed (AIFF, WAVE, AU, MP3), generated in real-time (sine, square, triangle and sawtooth waves, white and pink noise), manipulated (raw or via built-in filters), saved (AIFF, WAVE), analyzed (FFT) or simply played back. ESS - generazione Classi usate: Ess (metodi statici per il controllo generale del device audio) AudioStream (Stream audio di output monofonico) SineWave (generatore di forma d’onda) Esempio: EssExample1 ESS - riproduzione Per aprire un file mp3 è necessario scaricare e installare 3 componenti aggiuntivi (3 file jar relativi a librerie java per l’mp3). La classe che permette di aprire un file mp3 è AudioFile Esempio: EssExample2 Svg library Scalable Vector Graphics (SVG) is an XML specification and file format for describing two-dimensional vector graphics, both static and animated. SVG can be purely declarative or may include scripting. Images can contain hyperlinks using outbound simple XLinks. It is an open standard created by the World Wide Web Consortium's SVG Working Group. Apertura e manipolazione di un file E’ garantito il funzionamento con file creati da AI. Viene usata la classe SVG SVG svgImage = new SVG(this, “file.svg”); Il metodo draw() disegna l’immagine a schermo Essendo un immagine vettoriale, può essere trasformata in modo molto efficiente. Esempio: SvgExample Interfascia Libreria per la realizzazione di interfacce grafiche. Fornisce numerosi controlli: pulsanti, radio buttons, check box, slider, ecc. Gestisce automaticamente l’interazione attraverso un meccanismo a eventi. Esempio - pulsante Ogni widget è gestito da un tipo di variabile: IFButton, IFLabel, ecc. L’oggetto GUIController supervisiona il meccanismo degli eventi. Deve essere costruito a partire dalla applet che deve ricevere gli eventi (tipicamente, this): GUIController controller = new GUIController(this) Esempio: InterfasciaExample Listener e EventHandler Ad ogni controllo, deve essere associato un oggetto actionListener. In pratica, questo associa un contenitore (che è di soltio la applet stessa) al controllo. Quando avviene un evento, viene chiamato automaticamente l’eventHandler actionPerformed, con il parametro GUIEvent che contiene varie informazioni tra cui l’oggetto che ha scatenato l’evento. Gestione dell’evento La funzione actionPerformed contiene le istruzioni che devono essere eseguite quando si scatena l’evento. Il parametro GUIEvent ha un metodo getSource() che viene utilizzato per capire qual’è il controllo che ha originato l’evento. BlobDetection Trova i “blob” presenti nell’immagine. Un blob è una regione a intensità costante presente nell’immagine. L’immagine viene prima convertita in un’immagine b/n (attraverso un’operazione di sogliatura), e successivamente elaborata per trovare tutte le regioni a intensità costante. L’algoritmo restituisce i bordi e la bounding box di ogni regione trovata. Può servire come primo stadio di un sistema di rilevamento/riconoscimento oggetti. La classe BlobDetection fa tutto il lavoro. Il metodo setThreshold(float) setta il valore da utilizzare per la sogliatura Il metodo computeBlobs(PImage) esegue il calcolo dei blob Il risultato è una lista di oggetti Blob, che contengono le informazioni di ogni regione trovata. L’algoritmo non è molto efficiente, e funziona solo su immagini di piccole dimensioni. Esempio: BolobDetectionExample JMyron library Una interessante alternativa per l’analisi del video da telecamera è la libreria JMyron Non dipende da qt4java, e implementa un certo numero di algoritmi per l’analisi video [vedi reference] Hardware processing Esistono delle schede hardware (wiring, $80, arduino, $30) a micrcontrollore programmabili tramite un linguaggio (wiring) che è costruito a partire dal processing. Wiring condivide molte delle feature del processing, in più mette a disposizione gli strumenti per generare il codice nativo per i microcontrollori, e per scaricare il codice oggetto sulle schede. Mobile processing Esiste un versione del processing (mobile.processing.org) che può essere eseguita su qualsiasi dispositivo mobile dotato di java virtual machine. Processing mette a disposizione un simulatore di per sviluppare su tali dispositivi. Lo sviluppo prevede l’utilizzo di un subset delle feature del java. Temi per elaborati Sviluppo di un modulo per l’acquisizione video da telecamera IP per flusso di tipo mjpeg (funzionante con telecamere axis/sony) Sviluppo di un modulo per l’acquisizione video sotto linux da file o da sorgenti v4l/v4l2 tramite ffmpeg.