Proseguiamo nello studio delle canvas.
Tipi di linee
La specifica mette a disposizioni diverse varianti per modificare lo stile delle linee che è possibile disegnare:
- context.lineWidth [ = value ]: getter/[setter] per definire la dimensione (spessore) della linea.
- context.lineCap [ = value ]: getter/[setter] per definire il line cap style (lo stile con cui viene disegnata la terminazione della linea), i valori definibili sono butt, round, and square.
- context.lineJoin [ = value ]: getter/[setter] per definire il line join style (lo stile con cui vengono connesse due linee), può essere bevel round o meter
- context.miterLimit [ = value ]: getter/[setter] per definire il miterLimit, è utilizzabile solo quando il lineJoin=miter, evita per angolazioni molto strette di avere cuspidi troppo "spesse", quando ciò accade (il valore di miterLimit è superato) l’angolo viene renderizzato come bevel[led].
Vediamo un esempio che disegna diversi tipi di linee sul canvas:
<!doctype html>
<html>
<head>
<title>Tipi di linee</title>
<script src="jquery.js"></script>
<script>
// # Al DOM Ready
$(function() {
// # Ottengo l'oggetto relativo al canvas
var canvasObj = document.getElementById("c_area");
context = canvasObj.getContext("2d"); // recupero il contesto
// # Colore linea
context.strokeStyle = "#222";
context.beginPath();
// # linea con lineCap butt
// # tutte le linee avranno spessore 6
context.lineWidth = "6";
context.lineCap = "butt";
context.moveTo(60, 20);
context.lineTo(60, 120);
context.stroke();
context.closePath();
context.beginPath();
// # linea con lineCap round
context.lineCap = "round";
context.moveTo(80, 20);
context.lineTo(80, 120);
context.stroke();
context.closePath();
context.beginPath();
// # linea con lineCap square
context.lineCap = "square";
context.moveTo(100, 20);
context.lineTo(100, 120);
context.stroke();
context.closePath();
context.strokeStyle = "#990000";
context.beginPath();
// # Disegno due linee collegate con lineJoin = miter e
// miterLimitn = 3
context.lineJoin = "miter";
context.miterLimit = "3"
context.moveTo(130, 20);
context.lineTo(130, 120);
// # Prima di chiudere il path disegno la seconda linea
context.lineTo(200, 80);
context.stroke();
context.closePath();
});
</script>
</head>
<body>
<article>
<canvas id="c_area" height="300px">Impossibile mostrare il canvas</canvas>
</article>
</body>
</html>
Il risultato in Opera: da sinistra lineCap butt, poi round, square; a destra due linee contigue con lineJoin = miter.
Manipolazione per pixel
Le canvas APIs offrono funzionalità molto potenti che permettono di manipolare un’immagine a basso livello, si possono creare immagini da zero per poi riempirle pixel per pixel oppure modificare immagini esistenti. Ecco quali sono i metodi coinvolti:
- imagedata = context.createImageData(sw, sh): crea un oggetto ImageData di dimensioni sw, sy composto da pixels neri (transparent black).
- imagedata = context.createImageData(imagedata): crea un oggetto ImageData, con le dimensioni dell’oggetto preso in input, composto da pixel neri (transparent black)
- imagedata = context.getImageData(sx, sy, sw, sh): crea un oggetto ImageData contenente i dati dell’immagine per la porzione rettangolare del canvas delimitata dai parametri inviati. Qualora uno degli argomenti non fosse un numero finito o mancasse uno tra i parametri width ed height (sw, sh) il metodo lancerebbe una eccezione.
- imagedata.width, imagedata.height: getters riferiti alle dimensioni dell’oggetto ImageData (in pixels relativi al dispositivo).
- imagedata.data: getter per l’array (Canvas Pixel ArrayBuffer) contenente i dati RGBA (RedGreenBlueAlpha) sottoforma di numeri compresi tra 0 e 255.
- context.putImageData(imagedata, dx, dy [, dirtyX, dirtyY, dirtyWidth, dirtyHeight ]): disegna sul canvas una immagine le cui caratteristiche sono definite dall’oggetto ImageData. Qualora uno degli argomenti non rappresentasse un numero finito il metodo lancerebbe una eccezione, inoltre se fosse fornito un "dirty rectangle" ([, dirtyX, dirtyY, dirtyWidth, dirtyHeight ]) solo i pixels appartenenti a tale rettangolo sarebbero disegnati.
Vediamo un esempio concreto di manipolazione dei pixel di una immagine esistente, lo script mostrerà a sinistra l’immagine originale, a destra il risultato delle elaborazioni:
<!doctype html>
<html>
<head>
<title>Manipolazione dei pixels con le canvas APIs</title>
<script src="jquery.js"></script>
<script>
// # Funzione invocata all' onLoad
function afterLoad(event) {
// # Recupero l'oggetto canvas
canvasObj = document.getElementById("c_area");
// # Ottengo il contesto
context = canvasObj.getContext("2d");
// # Recupero l'immagine dall'evento
immagine = event.target;
// # Recupero width ed height del canvas
width = parseInt(canvasObj.getAttribute("width"));
height = parseInt(canvasObj.getAttribute("height"));
// # Disegno effettivamente l'immagine
context.drawImage(immagine, 0, 0);
// # Recupero i dati dell'immagine (array descrittivo dei pixels)
imageData = context.getImageData(0, 0, width, height);
// # halfXOffset rappresenta il punto delle ascisse dove inizia
// # il disegno della seconda immagine
var halfXOffset = Math.ceil(width/2);
// # Ciclo l'immagine pixel per pixel
for (y = 0; y < height; y++) {
// #¯pos pixel sorgente
srcPos = y * width * 4;
// # posizione traslata del pixel nella seconda immagine
outPos = srcPos + halfXOffset * 4
// # Ciclo interno
for (x = 0; x < halfXOffset; x++) {
// # Manipolazione dei pixels
imageData.data[outPos++] = imageData.data[srcPos++] / 2; // ROSSO
imageData.data[outPos++] = imageData.data[srcPos++] * 2; // VERDE
imageData.data[outPos++] = imageData.data[srcPos++]; // BLU
imageData.data[outPos++] = imageData.data[srcPos++]; // ALPHA
}
}
// # Disegno la nuova immagine a destra dell'originale
context.putImageData(imageData, 0, 0);
}
// # Al DOM Ready
$(function() {
// # Creo l'immagine
var immagine = new Image();
// # Definisco la funzione da invocare all'onLoad
immagine.onload = afterLoad;
// # Indicazione del file sorgente
immagine.src = "candeline.jpg";
});
</script>
</head>
<body>
<article>
<canvas id="c_area" height="400px" width="500px">Impossibile mostrare il canvas</canvas>
</article>
</body>
</html>
Il risultato:
Per ottenere il bianco e nero basta modificare il ciclo for interno in questo modo:
for (x = 0; x < halfXOffset; x++) {
var rosso = imageData.data[srcPos++];
var verde = imageData.data[srcPos++];
var blu = imageData.data[srcPos++];
var alpha = imageData.data[srcPos++];
// # Il valore è dato dalla somma dei canali fratto 3
var valoreBN = (rosso + verde + blu) / 3;
// # Manipolazione dei pixels
imageData.data[outPos++] = valoreBN; // ROSSO
imageData.data[outPos++] = valoreBN; // VERDE
imageData.data[outPos++] = valoreBN; // BLU
imageData.data[outPos++] = alpha; // ALPHA
}
Il risultato:
Possiamo anche modificare solamente il valore di alpha:
for (x = 0; x < halfXOffset; x++) {
// # I colori non cambiano
imageData.data[outPos++] = imageData.data[srcPos++];
imageData.data[outPos++] = imageData.data[srcPos++];
imageData.data[outPos++] = imageData.data[srcPos++];
// # impostiamo alpha mezzi
imageData.data[outPos++] = imageData.data[srcPos++] / 2;
}
ottenendo:
Per chiudere uno screenshot in cui possiamo notare grazie a FireBug la composizione dell’oggetto imagedata.data: un Uint8ClampedArray.