Una tela trasparente
Il canvas è un elemento HTML5 rappresentato dal tag <canvas>. Può essere inteso come il corrispettivo digitale di una tela trasparente: uno spazio all'interno di una pagina web sul quale insistere con specifiche API adatte a tracciare linee, cerchi, rettangoli, immagini e altro ancora. Il canvas è, in estrema sintesi, una grande matrice di pixel, ognuno dei quali modificabile singolarmente nelle sue quattro componenti RGBA, rosso, verde, blu e alpha, la trasparenza. Tutti siamo a conoscenza di questo elemento HTML5 per una sua semplice attitudine: la possibilità di replicare i comportamenti fino ad oggi appannaggio della tecnologia Flash. In questo articolo esploreremo le numerose API disponibili e ne sperimenteremo una buona parte per poi concentrarci successivamente su di una interessante evoluzione del progetto guida.
Le specifiche
Prima di addentrarci nella giungla dei vari metodi a disposizione per disegnare sul canvas vediamo brevemente l'architettura che contraddistingue il markup dell'elemento:
<canvas width="200px" height="200px" id="demo_canvas">
Contenuto da mostrare in caso il canvas non sia supportato.
(quindi anche informazioni per i motori di ricerca).
</canvas>
Gli unici due attributi disponibili, oltre ai canonici globali, sono width
e height
, rispettivamente larghezza e altezza dell'elemento.
Sotto il profilo JavaScript i metodi disponibili direttamente attraverso questo elemento sono due: toDataUrl
e getContext
. Il primo, toDataUrl
, è fantastico nella sua semplicità: ritorna infatti il contenuto del canvas secondo il protocollo 'data URL'. Questo schema memorizza file di ogni tipo in lunghissime sequenze di caratteri, che poi possono essere salvati su disco o, se come nel caso del canvas siano immagini, essere utilizzate come attributo src
di tag img
. Nei prossimi esempi vedremo un utilizzo tangibile di questo metodo, per ora invece concentriamoci su getContext
, vera e propria 'porta' verso le API di disegno. getContext
, quando invocato, ritorna un oggetto detto contesto del disegno:
var canvas = document.getElementById("demo_canvas");
var contesto = canvas.getContext("2d");
In questa lezione esploreremo solamente i metodi legati al contesto di disegno '2d', come specificato dall'argomento utilizzato nello snippet, ma sappiate che esiste già una versione sperimentale di contesto '3d' che sottende un set di API completamente diverso, basato sulle OpenGL ES 2.0 e detto WebGL.
Ma torniamo al contesto '2d': possiamo dividere le funzionalità in esso racchiuse in categorie: path, modificatori, immagine, testo e pixel per pixel; nelle prossime sezioni affronteremo in dettaglio ognuno di questi aspetti. Prima di proseguire è però importante evidenziare che il contesto è un oggetto stateful, che mantiene cioè al suo interno dei valori che influiscono sulle operazioni successive; aspettiamoci quindi delle direttive di questo tipo:
- imposta colore di linea verde;
- imposta percorso della linea da (0,0) a (10,10);
- traccia tutti i percorsi di linea.
In questo caso, se avessimo usato le corrette API, avremmo ottenuto una linea verde tra i punti (0,0) e (10,10). L'insieme dei valori memorizzati in un contesto viene detto drawing state.
Path
I metodi di questa categoria sono tutti funzionali al disegno di frammenti geometrici come linee, archi e quant'altro: ecco un esempio omnicomprensivo:
disegnaBarchetta = function(contesto){
contesto.beginPath();
// sposta il cursore del path nella posizione 40,170
contesto.moveTo(40,170);
// crea un subpath linea fino alla posizione 260,170
contesto.lineTo(260,170);
// crea un subpath arco che sia tangente alle due linee (260,170 - 150,250)
// (150,250 - 40,170) con diametro 150
contesto.arcTo(150,250,40,170,150);
contesto.lineTo(40,170);
contesto.moveTo(150,170);
contesto.lineTo(150,40);
contesto.rect(150,40,60,30);
// disegna tutti i subpath del path corrente sul canvas usando la
// configurazione del drawing state
contesto.stroke();
// riempi tutti le aree inscritte dal path corrente usanto la configurazione
// del deawing state
contesto.fill();
}
Il concetto principe è il path, ovverosia un insieme di punti uniti fra loro da definite primitive geometriche (arco, linea,...), dette subpath. Ogni contesto possiede solamente un path attivo per volta. Per aggiungere un subpath al path attivo si possono utilizzare metodi come lineTo
, arcTo
, rect
e altri. Metodi come stroke
e fill
vengono applicati a tutti i subpath del path attivo. Per resettare il path attivo, rimuovendo quindi tutti i subpath non ancora disegnati, si utilizza l'istruzione beginPath()
.
Eseguiamo l'esempio appena mostrato per ottenere il risultato seguente (figura 1):
Oltre ai subpath mostrati ce ne sono altri, ma il meccanismo di generazione resta sempre lo stesso, la lista completa è disponibile nel documento ufficiale delle specifiche.
Modificatori
Le funzioni all'interno di questo gruppo insistono sul drawing state di un canvas, modificando il modo in cui i path, le immagini ed i testi vengono disegnati. Nella categoria si annoverano matrici di trasformazione (scale
, rotate
, translate
), gestione della trasparenza globale e delle funzioni di composizione nonché impostazioni di colore, di linea e di gradiente. Suona complicato ? Facciamo un po' di prove:
contesto.scale(0.5,0.5);
contesto.rotate(0.5);
contesto.translate(300,-100);
disegnaBarchetta(contesto);
Ecco il risultato, i modificatori impostati vengono applicati alla barchetta (figura 2):
Utilizziamo ora trasparenza e composizione:
contesto.globalAlpha = 0.5;
disegnaBarchetta(contesto);
contesto.translate(40,-0);
contesto.globalCompositeOperation = "source-out";
disegnaBarchetta(contesto);
L'attributo globalAlpha
definisce (da 0.0 a 1.0) il livello di trasparenza che dovrà essere applicato a quanto verrà disegnato sul canvas. globalCompositeOperation
ci permette invece di specificare in che modo vogliamo che le immagini vengano sovrapposte: è possibile utilizzare un set di parole chiave per indicare un ben preciso comportamento; in questo caso con source-out
chiediamo allo user agent di effettuare una somma tra le trasparenze ottenendo un risultato come questo (figura 3):
Attenzione, al momento non tutti i browser reagiscono allo stesso modo alle definizioni legate a GlobalCompositeOperation
. Infine esaminiamo anche le operazioni legate ai modificatori di colore:
contesto.strokeStyle = "#FF0000";
contesto.lineWidth = "5";
var gradiente = contesto.createRadialGradient(150, 150, 30, 150, 160, 100);
gradiente.addColorStop(0.5, '#0000FF');
gradiente.addColorStop(0.1, '#000000');
contesto.fillStyle = gradiente;
disegnaBarchetta(contesto);
Con strokeStyle
e fillStyle
possiamo impostare le nostre preferenze su come intendiamo debba essere disegnata la linea di contorno degli oggetti (stroke) ed il loro riempimento (fill). Entrambi gli attributi possono essere valorizzati con stringhe, oggetti di tipo gradient
ed oggetti di tipo pattern
. In questo caso è mostrato l'utilizzo di una semplice stringa di colore rosso per lo stroke e di un gradient radiale con una variazione di colore dal nero al blu per il fill. L'utilizzo dell'oggetto pattern
è simile, il metodo da utilizzare è contesto.createPattern(immagine, opzionaleRipetizione)
dove il secondo argomento riprende le scelte effettuabili via CSS (no-repeat
, repeat-x
, ecc..) mentre il primo può essere valorizzato con con un elemento img
, un altro canvas o perfino un elemento video. Ecco il risultato che si ottiene utilizzando il codice visto (figura 4):
Prima di passare al prossimo gruppo di funzioni citiamo brevemente anche la possibilità di implementare un ombreggiatura, ecco le istruzioni:
contesto.shadowColor = '#003300'
contesto.shadowOffsetX = 10;
contesto.shadowOffsetY = 10;
contesto.shadowBlur = 30;
disegnaBarchetta(contesto);
Ed il risultato (figura 5):
Immagine
L'unico metodo appartenente a questo gruppo si chiama drawImage
ed è disponibile in un certo numero di varianti; il primo argomento può infatti essere sia un elemento immagine, sia un altro canvas, sia un elemento di tipo video. Il risultato dell'operazione è sempre quello di disegnare sul contesto invocante l'immagine prescelta, rispettando alcuni parametri dimensionali opzionali. Ecco un esempio:
var immagine = new Image();
immagine.src = "http://img227.imageshack.us/img227/7092/marei.jpg";
contesto.drawImage(immagine,0,0);
disegnaBarchetta(contesto);
E il risultato (figura 6):
In questo caso chiediamo che l'immagine sia disegnata a partire dall'angolo in alto a sinistra del canvas (coordinate 0,0); possiamo però anche specificare una dimensione di destinazione e perfino un rettangolo di partenza in modo da prelevare solo una porzione di immagine utilizzando i numerosi argomenti opzionali messi a disposizione dalle specifiche.
Testo
Come il nome del gruppo suggerisce, questi metodi permettono di scrivere porzioni di testo all'interno di un canvas, eccone un esempio:
disegnaBarchetta(contesto);
contesto.globalCompositeOperation = "source-out";
contesto.font = "34px Georgia"
contesto.strokeText("Va va va barchetta", 20, 100);
contesto.fillText("va, naviga naviga naviga", 20, 140);
contesto.fillText("naviga e mai si", 20, 180);
contesto.fillText("fermera'...", 20, 220);
L'attributo font
può essere valorizzato allo stesso modo del suo omonimo su CSS, per il resto fillText
provvede a scrivere il testo desiderato partendo dalla posizione x, y specificata come secondo e terzo argomento; anche strokeText
si comporta in modo simile ma con una piccola variante che si può facilmente evincere dal risultato dell'esecuzione dell'esempio. Da notare inoltre il comportamento interessante dell'applicazione di un particolare tipo di composizione (figura 7):
Pixel per Pixel
Come già anticipato in precedenza, il canvas non è nient'altro che una matrice di valori in formato RGBA: non stupisce che sia quindi possibile insistere su ogni singolo e specifico pixel della stessa; ecco un esempio:
disegnaBarchetta(contesto);
var dati_immagine = contesto.getImageData(0,0,
contesto.canvas.width, contesto.canvas.height);
var array_dati_immagine = dati_immagine.data;
for(var i = 0; i < array_dati_immagine.length; i +=4 ){
array_dati_immagine[i ] = Math.round(Math.random() * 255);
array_dati_immagine[i+1] = Math.round(Math.random() * 255);
array_dati_immagine[i+2] = Math.round(Math.random() * 255);
}
dati_immagine.data = array_dati_immagine;
contesto.canvas.width = contesto.canvas.width;
contesto.putImageData(dati_immagine, 0,0);
Il fulcro dello snippet è da ricercarsi nelle due funzioni getImageData
e putImageData
che rispettivamente prelevano e 'stampano' sul canvas una porzione di immagine delineata dagli argomenti passati. Tale porzione, un oggetto di classe ImageData
, possiede in una sua proprietà, data, un lunghissimo array contenente le componenti RGBA di ogni pixel. Il ciclo for presentato nel codice precedente insiste proprio su questo array assegnando ad ognuna delle tre componenti RGB un valore randomico. Ecco il risultato (figura 8):
Per una verifica diretta di quanto visto nel corso della lezione è disponibile questa demo.
Ora che abbiamo almeno un'idea delle potenzialità dello strumento che stiamo analizzando, accingiamoci ad una sperimentazione pratica attraverso il progetto guida. Lo vedremo nella prossima lezione.
Tabella del supporto sui browser
Grafica e Multimedia | |||||
---|---|---|---|---|---|
<canvas> | 9.0+ | 2.0+ | 3.1+ | 2.0+ | 10.0+ |