Forsiden UiO Universitetets senter for informasjonsteknologi
print logo

HTML5 Canvas – et eksempel med fraktaler

HTML5 er en spesifikasjon som etterhvert omfavner mye. En viktig del er nye mer semantiske HTML-elementer, hvorav flere har et tilhørende API som kan implementeres med JavaScript. Mye av spesifikasjonen er allerede støttet i de nyeste versjonene av nettleserene, mens Internet Explorer mangler mye før versjon 9 blir lansert.

Et av de kreative og morsomme nye elementene er Canvas. Med dette kan man tegne direkte i nettleseren uten ekstra tredjepartsprogramvare som Flash og Java (applets) – f.eks. fraktaler basert på Mandelbrotmengden som genereres rekursivt etter formelen:

zn+1 = zn2 + c

Bilde

Resultat

Rekalkuler
 

Fraktaler

Fraktaler er ofte selvlignende, og når man zoomer inn går strukturene igjen i det uendelige. Benoit Mandelbrot, som ga dem navnet fraktal (fra latinsk fractus som betyr brutt), brukte dem bl.a. matematisk for å beskrive lengden på kystlinjen til Storbritannia. De kan ellers brukes for å beskrive alt fra lungestrukturer, dyremønstre og værsystemer til distribusjonen av materie i universet.

Jeg synes det er spennende at veldig enkle formler kan skape komplekse og uventede ting, og har derfor tatt utgangspunkt i pseudokoden som ligger på Wikipedia, en implementasjon med fillrect() og en bloggartikkel om pikselmanipulasjon for å lage fraktaler med Canvas-elementet.

Få tilgang til lerretet

Først må man definere Canvas slik i HTML-en:

<canvas height="548" id="canvas1" width="730">
  HTML5 Canvas støttes ikke.
</canvas>

Linjene under gir tilgang til Canvas-elementet og lager bildedata-array:

var element = document.getElementById(id),
    c = element.getContext("2d"),
    width = parseInt(element.getAttribute("width")),
    height = parseInt(element.getAttribute("height")),
    imgd = c.createImageData(width, height),
    pixData = imgd.data,

Man kan deretter iterere pY og pX og sette piksel for piksel i arrayet. Jeg har valgt å iterere pY med setTimeout() for å minimere heng i nettleseren (Web Workers kunne også vært brukt; der bildedataene returneres fra arbeideren når ferdig og skrives til Canvas-elementet).

Mandelbrot-algoritmen

max = 200;
..
while ( x*x + y*y <= 4 && i < max) {
   xtmp = x*x - y*y + x0;
   y = 2*x*y + y0;
   x = xtmp;
   i++;
}

Variablen max er hvor detaljert man vil ha Mandelbrot-fraktalen (typisk fra 1 til 1000).

Jevnere overganger

Man kan deretter bruke en metode for normalisering av i for å få jevnere overganger mellom fargene:

mod = Math.sqrt(x*x + y*y);
mu = i + 1 - Math.log(Math.log(mod)) / log2;

Farger

For å lage farger har jeg brukt i både som fargetone og lysverdi i HSV og konvertert til RGB:

hueShift = 60,
hueStretch = 2;
..
i = 360 / (mu / 7);
RGB = hsvToRgb((i - hueShift) / hueStretch, 100, i);
r = RGB[0];
g = RGB[1];
b = RGB[2];

Variabelen hueShift flytter fargetonene, mens hueStretch gjør fargeovergangene lengre.

Sette én piksel

index = (pX + pY * width) * 4;
pix[index+0] = r;
pix[index+1] = g;
pix[index+2] = b;
pix[index+3] = 0xff;

Hver pixel tar 4 plasser i arrayet. Disse plassene definerer fargene rød, grønn og blå i tillegg til gjennomsiktighet (alfakanal).

Zoom og posisjonering

z = 20137,
cX = -0.215,
cY = 0.69,

Med disse kan man zoome og navigere fraktalen med f.eks. input-felter eller musebevegelser.

JavaScript-koden (uten hsvToRGB())

/*
 * Simple Mandelbrot fractals with Canvas
 *
 * by Øyvind Hatland
 *
 * Based on:
 * http://www.hoagieshouse.com/mandelbrot.html
 * http://en.wikipedia.org/wiki/Mandelbrot_set
 * http://beej.us/blog/2010/02/html5s-canvas-part-ii-pixel-manipulation/
 * http://snipplr.com/view/14590/hsv-to-rgb/
 *
 */

 var mandelbrot = function(id, zoom, xPos, yPos, maxIterations, colorShift, colorStretch) {
   var element = document.getElementById(id),
       c = element.getContext("2d"),
       width = parseInt(element.getAttribute("width")),
       height = parseInt(element.getAttribute("height")),
       imgd = c.createImageData(width, height),
       pixData = imgd.data, pY = 0;

   setTimeout(function() {
     var hsvToRgbFunc = hsvToRgb, pix = pixData,
         h = height / 2, w = width / 2,
         z = zoom, cX = xPos, cY = yPos,
         max = maxIterations,
         pX = 0, x, y, x0, y0, i, xtmp,
         mod, mu, log2 = Math.log(2),
         RGB = [], 
         hueShift = colorShift,
         hueStretch = colorStretch,
         r, g, b, index;

     for (; pX < width; pX++) {
       x0 = (pX - w) / z + cX;
       y0 = (pY - h) / z + cY;
       x = 0; y = 0;
       i = 0;
       while(x*x + y*y <= 4 && i < max) {
         xtmp = x*x - y*y + x0;
         y = 2*x*y + y0;
         x = xtmp;
         i++;
       }
       mod = Math.sqrt(x*x + y*y);
       mu = i + 1 - Math.log(Math.log(mod)) / log2;
       if(isNaN(mu)) {
         r=0; g=0; b=0;
       } else {
         i = 360 / (mu / 7);
         RGB = hsvToRgbFunc((i-hueShift) / hueStretch, 100, i);
         r = RGB[0];
         g = RGB[1];
         b = RGB[2];
       }
       index = (pX + pY * width) * 4;
       pix[index+0] = r;
       pix[index+1] = g;
       pix[index+2] = b;
       pix[index+3] = 0xff;
     }
     pY++;
     if(pY < height) {
       setTimeout(arguments.callee, 1);
     }
     c.putImageData(imgd, 0,0);
   }, 1);
 };

 mandelbrot("canvas1", 20137, -0.215, 0.69, 200, 60, 2);
 
 var calcBtn = document.getElementById("recalc"),
     reZoom = document.getElementById("zoom"),
     reXPos = document.getElementById("xpos"),
     reYPos = document.getElementById("ypos"),
     reMax = document.getElementById("max"),
     reColorShift = document.getElementById("color-shift"),
     reColorStretch = document.getElementById("color-stretch");

 calcBtn.onclick = recalc;

 function recalc(e) {
   if (!e) var e = window.event;

   mandelbrot("canvas1", parseFloat(reZoom.value), parseFloat(reXPos.value),
              parseFloat(reYPos.value), parseFloat(reMax.value),
              parseFloat(reColorShift.value), parseFloat(reColorStretch.value));
 }

Andre

Google har nylig lansert http://juliamap.googlelabs.com/ som bruker Web Workers for å spre kalkulasjonene over CPU-kjernene, og Google Maps API for å navigere fraktalene.

Noen flere:

Emneord: canvas, fraktaler, html5, javascript, mandelbrot Av Øyvind Hatland
Publisert 10. feb. 2011 22:00 - Sist endret 17. okt. 2011 18:39

Hei Øyvind. Takk for fin artikkel! Jeg oppdaget nettopp en annen flott site for fraktaler, og da spesielt 3D-fraktaler for WebGL: <fractal.io>

Her bør man altså ha en nettleser med god støtte for WebGL, og av de jeg har prøvet, så funker Chrome på Mac best. Jeg har prøvet Firefox 4 både på Mac og PC, men det har ikke funket noe videre med disse 3D-fraktalene.

/Jon

Dagfinn Bergsager - 18. okt. 2011 12:41
Legg til kommentar

For å kommentere på dette dokumentet må du logge inn

Hvis du ikke har UiO- eller Feide-brukernavn kan du opprette en WebID-bruker.