In questa lezione vedremo come manipolare flussi video in tempo reale con l'ausilio di un canvas. Perché il tutto funzioni è necessario che sia il video che la pagina che costruiremo risiedano sullo stesso dominio. Questo per prevenire l'intervento di alcune policy di sicurezza del browser che impediscono di fatto la modifica di materiale multimediale che non provenga dalla stessa origine della pagina. Ecco uno schema di quanto realizzeremo:
Utilizzeremo due canvas. Nel primo replicheremo i fotogrammi del video, mentre nel secondo andremo a inserire il risultato della nostra elaborazione sull'array di pixel estratti dal primo. Il procedimento sarà quindi il seguente:
- Avviare la riproduzione del video;
- Utilizzare la funzione
drawImage
per disegnare sul primo canvas il contenuto del fotogramma corrente del video; - Con la funzione
getImageData
recuperare dal canvas il fotogramma precedentemente disegnato, questa volta però come array di pixel modificabili; - Effettuare un ciclo su tutti i pixel recuperati apportando le modifiche volute;
- Utilizzare la funzione
putImageData
per disegnare l'array di pixel modificato sul secondo canvas; - Ripetere dal secondo punto la procedura.
Ecco il codice necessario:
<!doctype html>
<html>
<head>
<title> Canvas e Video </title>
<script>
decomposizioneColori = function(video, context, context_nascosto, colori){
if(video.paused || video.ended) return false;
context_nascosto.drawImage(video,0,0);
var fotogramma = context_nascosto.getImageData(0,0,
context_nascosto.canvas.width,context_nascosto.canvas.height);
var fotogramma_data = fotogramma.data;
var rosso = colori.rosso.checked;
var verde = colori.verde.checked;
var blu = colori.blu.checked;
for(var i=0; i < fotogramma_data.length; i+=4){
if (!rosso) fotogramma_data[i ] = 0;
if (!verde) fotogramma_data[i+1] = 0;
if (!blu ) fotogramma_data[i+2] = 0;
}
fotogramma.data = fotogramma_data;
context.putImageData(fotogramma,0,0);
setTimeout(function(){
decomposizioneColori(video, context, context_nascosto, colori)
},0);
}
aspettaIlPlay = function(evento){
var video = document.querySelector('video');
video.addEventListener("play", function(evento){
var context = document.querySelector('#canvas_principale').getContext('2d');
var context_nascosto = document.querySelector('#canvas_elaborazione').getContext('2d');
context.canvas.width = context_nascosto.canvas.width = video.clientWidth;
context.canvas.height = context_nascosto.canvas.height = video.clientHeight;
decomposizioneColori(evento.target,context,context_nascosto, document.forms.colori);
},true);
}
window.addEventListener("load",aspettaIlPlay,true);
</script>
</head>
<body>
<canvas id="canvas_principale"></canvas>
<canvas id="canvas_elaborazione" style="display:none;"></canvas>
<video poster="big_buck_bunny/poster.jpg" controls>
<source src="big_buck_bunny/trailer.mp4" type="video/mp4" >
<source src="big_buck_bunny/trailer.ogg" type="video/ogg" >
<source src="big_buck_bunny/trailer.webm" type="video/webm">
</video>
<form name="colori">
<fieldset>
<legend>Usa le checkbox per controllare i tre canali (RGB)</legend>
<label> Rosso <input type="checkbox" name="rosso" checked></label>
<label> Verde <input type="checkbox" name="verde" checked></label>
<label> Blue <input type="checkbox" name="blu" checked></label>
</fieldset>
</form>
</body>
</html>
Il funzionamento si concentra in decomposizioneColori
; qui vengono eseguite tutte le operazioni che abbiamo citato. All'interno del ciclo che scorre tutti i pixel (nelle componenti RGBA) un semplice controllo pilotato da una form 'spegne' le componenti di colore corrispondenti ad una checkbox non spuntata con un risultato simile a quello dell'immagine seguente:
Merita un cenno anche l'interessante uso della funzione requestAnimationFrame; il W3C ha predisposto questo metodo per meglio coordinare la gestione di animazioni all'interno di documenti web. A differenza dei suoi 'cugini' setTimeout e setInterval questa funzione applica alcune tecniche per ottimizzare le perfomance e mantenere basso l'utilizzo delle risorse del sistema. Purtroppo la maggior parte dei browser la considera ancora sperimentale e quindi antepone il proprio prefisso (moz, o, ms o webkit). Per risolvere questo problema esistono alcuni snippet di codice come quello proposto da Paul Irish.
Possiamo verificare il tutto nella demo conclusiva.
Conclusioni
In queste ultime due lezioni abbiamo esplorato tutto l'ecosistema che circonda la nascita del tag video
. Prima di passare alla prossima lezione ricordiamo che intorno a questo elemento già si affacciano le prime librerie, capaci di aiutare nella personalizzazione del player e nella gestione di fallback su altre tecnologie nel caso l'HTML5 non sia supportato. In particolare segnaliamo l'ottimo video.js, che sicuramente merita una visita di approfondimento.