HTML5:Manipolare i Pixel con il Canvas
Nei precedenti post sul Canvas ho introdotto le API fondamentali del context 2D e come gestire le animazioni con questo elemento HTML5 che Internet Explorer 9 possiamo sfruttare avvalendoci della accelerazione grafica garantita dal nuovo browser.
Un'altra interessante potenzialità del Canvas che a partire dalla RC di IE9 è stata completata e migliorata significativamente dal punto di vista delle performance, riguarda la possibilità di utilizzare le API del Canvas che consentono via JavaScript di manipolare direttamente a livello di Pixel con un approccio RGBA il contenuto del Canvas, consentendo l'applicazione di trasformazioni e manipolazioni del contenuto, utili per costruire interessanti effetti grafici nelle nostre applicazioni Web. La principale API disponibile sul context 2D del Canvas che consente l'accesso ai pixel è il metodo getImageData(posX,posY,imgWidth,imgHeight) . Questo metodo restituisce un CanvasPixelsArray che contiene essenzialmente i pixel dell'immagine estratta nella proprietà data alla posizione e con le dimensioni passate al metodo. E' possibile accedere e modificare il contenuto dell'array contenente i pixel e riapplicare l'arrai con le modifiche ad un Canvas in una specifica posizione con il metodo putImageData(pixelsArray,posX,posY) .
Il CanvasPixelsArray consente di accedere ai pixel come abbiamo detto, con una struttura rgba.Contiene praticamente per ogni pixel 4 posizioni, 3 contengono rispettivamente i valori da 0 a 255 RGB e una contiene l'alpha . Ad esmpio per accedere al parametro alpha del primo pixel estratto con getImageData, occorre accedere all'elemento nella posizione 3 dell'Array, mentre ad esempio nella posizione 4 troviamo il valore R del secondo pixel estratto.
Di seguito abbiamo un semplice esempio che illustra praticamente l'utilizzo delle API. Nell'esempio viene caricata un immagine in un Canvas e poi accedendo ai pixel, invertito il valore del R di tutti i pixel e ridisegnata l'immagine manipolata nel Canvas:
<!DOCTYPE html />
<html lang="en">
<head>
<title></title>
<script type="text/javascript" language="javascript">
this.onload = function () {
var myCanvas = document.getElementById('myCanvas');
if (myCanvas.getContext) {
var ctx = myCanvas.getContext("2d");
var img = new Image();
img.src = "IELogo.png";
img.onload = function () {
ctx.canvas.height = img.height;
ctx.canvas.width = img.width;
ctx.drawImage(img, 0, 0);
var pixelsArray = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
var data = pixelsArray.data;
for (var i = 0; i < data.length; i += 4) {
data[i+1] = 255 - data[i]; //invert G in pixel
}
ctx.putImageData(pixelsArray, 0, 0);
}
}
}
</script>
</head>
<body>
<canvas id="myCanvas">
Canvas is not supported.
</canvas>
</body>
</html>
Altra interessante API del Canvas che permette di estrarre i pixel e salvarli anche in un file o rileggerli come un immagine direttamente è il metodo toDataURL("image/png") . La specifica prevede il supporto del formato png con questo metodo e utilizzando questa API si può direttamente estrarre l'immagine. Ad esempio, rimanipolando l'esempio precedente possiamo aggiungere un tag image e quindi , dopo aver impostato a display:none il canvas, disegnare nel tag image il risultato della modifica dell'immagine:
<!DOCTYPE html />
<html lang="en">
<head>
<title></title>
<script type="text/javascript" language="javascript">
this.onload = function () {
var myCanvas = document.getElementById('myCanvas');
var im = document.getElementById('im');
if (myCanvas.getContext) {
var ctx = myCanvas.getContext("2d");
var img = new Image();
img.src = "IELogo.png";
img.onload = function () {
ctx.canvas.height = img.height;
ctx.canvas.width = img.width;
ctx.drawImage(img, 0, 0);
var pixelsArray = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
var data = pixelsArray.data;
for (var i = 0; i < data.length; i += 4) {
data[i + 1] = 255 - data[i]; //invert G
}
ctx.putImageData(pixelsArray, 0, 0);
im.src = ctx.canvas.toDataURL("image/png");
}
}
}
</script>
</head>
<body>
<canvas id="myCanvas" style="display: none"" >
Canvas is not supported.
</canvas>
<img id="im" />
</body>
</html>
E' possibile anche manipolare i contenuti partendo dal catturare le immagini da un video in riproduzione in un tag video. Di seguito un esempio di utilizzo di questa API per ridisegnare un video che ha uno sfondo uniforme, su uno sfondo differente. Viene usata una funzione js con un interval che ogni 50 millisecondi disegna il frame corrente del video in riproduzione su un canvas nascosto e accedendo con getImage() ad ogni singolo pixel, porta a zero l'alpha dei pixel completamente blu . La funzione prende poi l'immagine manipolata e la ridisegna su un canvas visibile dove viene impostata una immagine differente come background, producendo così la riproduzione del video nel Canvas su un background differente, utilizzando direttamente le API del browser del canvas e del tag video.
Di seguito il codice della pagina:
<!DOCTYPE html>
<html>
<head>
<style>
body
{
background: black;
color: #CCCCCC;
}
#c2
{
background-image: url('Background.jpg');
background-repeat: no-repeat;
}
div
{
float: left;
border: 1px solid #444444;
padding: 10px;
margin: 10px;
background: #3B3B3B;
}
</style>
<script type="text/javascript">
var processor = {
timerCallback: function () {
if (this.video.paused || this.video.ended) {
return;
}
this.computeFrame();
var self = this;
setTimeout(function () {
self.timerCallback();
}, 50);
},
doLoad: function () {
this.video = document.getElementById("video");
this.c1 = document.getElementById("c1");
this.ctx1 = this.c1.getContext("2d");
this.c2 = document.getElementById("c2");
this.ctx2 = this.c2.getContext("2d");
var self = this;
this.video.addEventListener("play", function () {
self.width = self.video.videoWidth / 2;
self.height = self.video.videoHeight / 2;
self.timerCallback();
}, false);
},
computeFrame: function () {
this.ctx1.drawImage(this.video, 0, 0, this.width, this.height);
var frame = this.ctx1.getImageData(0, 0, this.width, this.height);
var l = frame.data.length / 4;
var data = frame.data;
for (var i = 0; i < l; i++) {
var r = data[i * 4 + 0];
var g = data[i * 4 + 1];
var b = data[i * 4 + 2];
if (g == 0 && r == 0 && b == 255)
data[i * 4 + 3] = 0;
}
this.ctx2.putImageData(frame, 0, 0);
return;
}
};
</script>
</head>
<body onload="processor.doLoad()">
<div>
<video id="video" src="video.mp4" controls width="336" height="256" />
</div>
<div>
<canvas id="c1" width="336" height="256" style="display: none">
</canvas>
<canvas id="c2" width="336" height="256">
</canvas>
</div>
</body>
</html>
e qui il risultato nel browser: