HTML5 Canvas e Frattali

Tra le novità più interessanti di HTML5 c’è il tag Canvas che permette di visualizzare un’immagine raster generata real time. In questo articolo troverete un esempio di applicazione, un frattale, realizzata proprio col canvas.

Il tag Canvas ha un’origine un po’ travagliata. È stato introdotto da Apple in Safari per colmare l’eterna lacuna dell’HTML che impediva di visualizzare immagini raster (cioé delle bitmap) generate realtime sul browser. In realtà già da tempo esisteva SVG e quindi la possibilità di realizzare immagini vettoriali ben più utili, ma Apple decide diversamente realizzando un elemento poco integrato nel DOM, indigesto a molto, ma molto pratico per alcune applicazioni.

Il Canvas è molto semplice da usare. Una volta definita l’area di rendering si può utilizzare Javascript per disegnarci sopra utilizzando le più comuni primitive di grafica 2D: linee, curve, rettangoli, ecc… Per questo esempio ho pensato una un qualcosa da disegnare che fosse anche interessante: il classico frattale di Mandelbrot. Incominciamo subito.

Incominciamo col definire una semplice pagina HTML che contenga il nostro oggetto canvas dotato di un identificativo.

<!DOCTYPE html>
<html>
<head>
<title>HTML5 e Frattali</title>
</head>
<body>
<canvas id="fractal" width="480" height="320">Il tuo browser fa schifo!</canvas>
</body>
</html>

Il testo contenuto nel tag è solo un fallback nel caso in cui l’utente non usi un browser recente. Magari mettete qualcosa di più simpatico e costruttivo.

Preso l’algoritmo da qui ci rendiamo conto che andremo a disegnare pixel per pixel, ma scorrendo le API del Canvas scopriamo che non esiste un metodo che ci consenta di farlo. Quel che si fa di solito in questi casi è di utilizzare delle linee lunghe un pixel, ma è una soluzione orrenda. Il canvas consente però di selezionare un’area del canvas e di agire direttamente sulle componenti RGBA di ogni pixel manipolando un array. Molto flessibile, ma un po’ rozzo. Pertanto realizziamo una funzione putPixel() che sia più pratica da utilizzare. Per comodità comincio a costruire il tutto sottoforma di oggetto.

function $(id) { return document.getElementById(id) }

o = {
  init: function() {
    this.canvas = $('fractal');
    if (!this.canvas.getContext) return;
    this.ctx = this.canvas.getContext('2d');
    this.cd = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height);

    putPixel(100, 100, {r: 255, g: 0, b: 0});

    this.ctx.putImageData(this.cd, 0, 0);
  },

  putPixel: function (x, y, color) {
    var idx = (x + y * this.canvas.width) * 4;
    this.cd.data[idx] = color.r;
    this.cd.data[idx + 1] = color.g;
    this.cd.data[idx + 2] = color.b;
    this.cd.data[idx + 3] = 255;
  },

  getPixel: function (x, y) {
    var idx = (x + y * this.canvas.width) * 4;
    return {
      r: this.cd.data[idx],
      g: this.cd.data[idx + 1],
      b: this.cd.data[idx + 2],
      a: this.cd.data[idx + 3]
    }
  },

}

Alla prima riga c’è la funzione dollaro è solo un modo sintetico per recuperare un elemento con un determinato ID. La funzione init() sarà chiamata al caricamento della pagina ed è il cuore del nostro esempio che in questa prima versione che nelle prime due righe recupera il riferimento al nostro canvas e verifica se contiene un metodo di nome getContext. L’assenza di questo metodo ci induce a pensare che il browser non supporti i canvas, per cui usciamo e non facciamo nulla.

La terza riga ottiene un riferimento al context del canvas. Si tratta dell’oggetto che ci darà accesso alle API del canvas. Il parametro passato indica che vogliamo il context 2D, che in realtà è l’unico che esiste, ma in futuro ce ne potrebbe essere anche uno 3D.

La quarta riga seleziona tutta l’area del canvas per la manipolazione dei singoli pixel con il metodo getImageData. A questo punto ipotizziamo di voler inserire un solo pixel rosso in posizione 100,100. Chiamiamo quindi la nostra funzione putPixel che prende le coordinate X,Y e un oggetto che contiene le componenti RGB. Basta un’occhiata alla funzione per capire come funziona l’array ottenuto da getImageData: tutti i pixel sono presi riga per riga e ogni pixel occupa quattro elementi dell’array perché è definito dai quattro parametri RGBA (rosso, verde, blue e trasparenza). putPixel calcola quindi l’indice della componente rossa a partire dalle coordinate e inserisce le altre di conseguenza. I valori sono dati nel range 0-255 e per semplicità ho imposto il valore di trasparenza a 255, ovvero completamente opaco.
Ho anche inserito l’equivalente funzione getPixel. Nel nostro esempio non avrà un ruolo, ma magari a qualcuno può tornare utile.

Infine c’è una chiamata a putImageData che prende l’array e lo disegna sull’area del canvas.

A questo punto abbiamo tutto quello che ci serve per poter disegnare il nostro frattale. Al posto della putPixel vista prima inseriamo del codice un po’ più corposo.

  var w = this.canvas.width;
  var h = this.canvas.height;
  var w2 = Math.floor(w / 2);
  var h2 = Math.floor(h / 2);

  for (var py = 0; py < h; py++) for (var px = 0; px < w; px++) {
    var x0 = (((px - w2) / w) * 4);
    var y0 = (((py - h2) / h) * 4);
    var x = 0;
    var y = 0;
    var iteration = 0;
    var max_iteration = 100;

    while ((x*x + y*y <= 4) && (iteration++ < max_iteration)) {
      var xtemp = x*x - y*y + x0;
      y = 2*x*y + y0;
      x = xtemp;
    }

    // Calcolo del colore del pixel
    if (iteration >= max_iteration) {
      color = {r: 0, g: 0, b: 0};
    } else {
      color.r = color.g = color.b = iteration * 255 / max_iteration;
    }

    this.putPixel(px, py, color);
  }

Sostanzialmente è l’implementazione in Javascript dell’algoritmo visto su Wikipedia. È l’esecuzione ricorsiva di funzione matematica che termina con una soluzione o viene abortita dopo un certo numero di cicli. Se viene abortita disegniamo un pixel nero (R,G,B = 0,0,0), altrimenti un pixel più o meno grigio in base al numero di cicli compiuti. Aumentando il numero massimo ci cicli otteniamo un’immagine più definita, ma ci mettiamo di più.

L’articolo potrebbe terminare qui, ma ci aggiungiamo ancora un mattone. Il grigio rende bene l’idea, ma è più divertente avere un frattale colorato. Ma non voglio realizzare un’immagine monocromatica, ma voglio agire sulle tonalità. Aggiungiamo quindi al nostro oggetto una funzione che trasforma un valore HSV (tonalità, saturazione, valore) in un valore RGB.

  hsv2rgb: function(h, s, v) {
    var m, n, f, i;
    if (h == null) return {r: v, g: v, b: v};
    i = Math.floor(h);
    f = h - i;
    if (!(i&1)) f = 1 - f; // if i is even
    m = v * (1 - s);
    n = v * (1 - s * f);
    switch (i) {
      case 0:
      case 6: return {r: v, g: n, b: m};
      case 1: return {r: n, g: v, b: m};
      case 2: return {r: m, g: v, b: n};
      case 3: return {r: m, g: n, b: v};
      case 4: return {r: n, g: m, b: v};
      case 5: return {r: v, g: m, b: n};
   }
  },

Questa funzione prende un parametro di tonalità in intervallo 0-6 e i due parametri di saturazione e valore nell’intervallo 0-1 e restituisce un oggetto contenente i tre valori RGB nell’intervallo 0-1.

Trasformiamo quindi il calcolo del colore del pixel visto prima in un qualcosa del genere.

      if (iteration >= max_iteration) {
        color = {r: 0, g: 0, b: 0};
      } else {
        color = this.hsv2rgb((iteration * 6 / this.max_iteration), 1, 1);
        color.r = Math.round(color.r * 255);
        color.g = Math.round(color.g * 255);
        color.b = Math.round(color.b * 255);
      }

Adesso siamo veramente alla fine. L’esempio che potete scaricare qui e che trovate nell’iframe qui sotto è ancora più completo con la possibilità di navigare il frattale e il calcolo del tempo necessario alla sua visualizzazione. Spero che questo articolo sia tanto utile quando il divertimento nell’averlo scritto.

files/files/

4 Commenti per “HTML5 Canvas e Frattali”

Commento di
fade
Inserito: 05/09/2010 15:04

quala? cheddice?
(alla terza riga su “immagine raster generata real time” mi sono fermata, per me è veramente troppo!)
Mu

Commento di
Caribe 1999
Inserito: 05/09/2010 21:59

È un articolo per chi ne capisce 🙂

Commento di
Idontknow
Inserito: 26/09/2010 11:04

Per me rimane sempre html, un linguaggio markup, preferisco usare php se devo creare/modificare immagini, c’è più fluidità..

Commento di
Caribe 1999
Inserito: 26/09/2010 20:48

Se devi visualizzare un’immagine statica ovviamente conviene generarla via PHP. Ma se vuoi che sia modificabile al volo sul browser allora canvas è un’ottima scelta.

Commenta