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
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:
- http://www.kevs3d.co.uk/dev/lsystems/ (forskjellige typer fraktaler)
- http://www.scale18.com/canvas2.html
- http://dougx.net/fractals/fractals.html
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
For å kommentere på dette dokumentet må du logge inn
Hvis du ikke har UiO- eller Feide-brukernavn kan du opprette en WebID-bruker.