back to top

L’elemento canvas e le canvas API (parte 4) – Tipi di linee e manipolazione per pixel

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.

lines

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:

green

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:

grayscale

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:

alpha

Per chiudere uno screenshot in cui possiamo notare grazie a FireBug la composizione dell’oggetto imagedata.data: un Uint8ClampedArray.

imagedata
Pubblicitร 

In questa guida...