Code snippets, ideas and events from IT related projects

by Robert Gawron

Rysowanie fraktali w przeglądarce cz.1: JavaScript + canvas

Canvas jest elementem HTML pozwalającym na rysowanie grafiki po stronie usera [np. dzięki JS]. Co do wymagań: As stated above, the <canvas> element isn't supported in all modern browsers so you will need Firefox 1.5, another recent Gecko-based browser, Opera 9, or a recent version of Safari to see all the examples in action.

Wpadłem na pomysł, by przybliżyć czytelnikom ten tag, dodatkowo pokazując parę fajnych fraktali, które można wygenerować na żywo u siebie. Żadnego ściągania i instalowania softu! :)

Zacznijmy od paprotki Barnsleya Przy każdym fraktalu podaję link do strony w wikipedii, gdzie jest podany algorytm jego generowania. BTW niesamowite jest, iż twory o fascynujacych właściwościach [np. prosta tak wygięta iż tworzy płaszczyznę] dają się opisać bardzo prostymi wzorami.

click to draw me [and wait]..

Liść taki jest samo podobny, czyli powiększając dany fragment odpowiednio długo otrzymamy obraz pierwotny [to jedna z cech fraktali]. Oczywiście tak jest w świecie matematyki, tutaj mamy skończoną rozdzielczość.

Na zajęciach z systemów dynamicznych kiedyś wykładowca wygenerował nam takie coś na komputerze swoim programem (w C). Cała sala zrobiła 'wooow!', ehh to był drugi rok chyba.. teraz to już nie robi na mnie wrażenia bo to kilka linijek kodu :) Kod źródłowy funkcji:

// fern fractal
function drawFern(canvasID){
    // get canvas to be able to draw into it

    var canvas = document.getElementById(canvasID)
    var ctx = canvas.getContext("2d")

    var max_step=20000
    var x=0
    var y = 0

    for(var i=0; i<max_step; i++){
        var r = Math.random()
        if(r <= 0.01){
            x = 0
            y = 0.16*y
        }
        else if(r <= 0.08){
            x = 0.2*x-0.26*y;
            y = 0.23*x+0.22*y+1.6;
        }
        else if(r <= 0.15){
            x = -0.15*x+0.28*y;
            y = 0.26*x+0.24*y+0.44;
        }
        else {
            x = 0.85*x+0.04*y;
            y = -0.04*x+ 0.85*y+1.6;
        }
        // scale fractal to best fit to the canvas object
        var zoom = 37
        x_print = width - (x*zoom +width/2)
        y_print = height - (y*zoom)
        dot(ctx, x_print, y_print, 1)
    }
}
kod funkcji stawiającej piksel na ekranie:
// put pixel on the canvas, ctx is canvas handler
function dot(ctx, x, y, pixSize) {
    ctx.save();
    ctx.fillStyle = dotColor;
    ctx.fillRect(x, y, pixSize, pixSize);
    ctx.restore();
}
do tego trzeba zdefiniować jeszcze jaka jest długość i szerokość ekranu by obrazki były optymalnie wyskalowane oraz podać jaki będzie kolor stawianego piksela:
var width = 400,
    height = 400,
    dotColor = '#eee';
..a samo wywołanie rysowania, tag i canvas mają się tak:
<b><a href="JavaScript:drawFern('canvasFern')"  style="font-size:120%;color:black;">click to draw me [and wait]..</a></b>
<canvas id="canvasFern" width="400" height="400" style="background:#6D7B8D;margin-top:5px;"></canvas>

Kolejnym fraktalem jest Odwzorowanie logistyczne, od takie sobie, jednak powiększając dostatecznie długo odpowiednie fragmenty również znów dostajemy pierwotny obrazek. Można się pobawić zmieniając parametry, tak, by zobaczyć tylko kawałek wykresu, ale za to bardzo powiększony, tak by otrzymać znów pierwotny obrazek.

click to draw me [and wait, don't stop the script!]..

Całość działa tak, iż bierzemy punkt ta płaszczyźnie, poddajemy go n razy równaniu logistycznemu i patrzymy, gdzie wylondowaliśmy[wiem, nie ma takiego słowa] (tam, gdzie wylondowalismy to wartość Y na wykresie). Ok, nie powiedziałem o masie teorii, tak czy owak teoria do tego jest duża i bogata (a sam jej już nie pamietam). Kod źródłowy:

// logistic fractal
function drawLogistic(canvasID){
    // get canvas to be able to draw into it
    var canvas = document.getElementById(canvasID)
    var ctx = canvas.getContext("2d")

    var a_r = 2.95,
        ra = 2.95,
        rb = 4.9,
        step = 0.0001,
        a_x = 0.9;
    for(a_r = ra; a_r < rb; a_r += step*(rb-ra) ){
        for(var i=0; i<90*a_r; i++){
            a_x = a_r * a_x * (1-a_x);
            if(i>80*a_r)// here i say that 80=Infinity :)
                dot(ctx, width*(a_r-ra), height*a_x, 0.5)
        }
    }
}

Kolejnym, bardzo sympatycznym, bo opartym na płaszczyźnie zespolonej fraktalem jest Zbiór Julii Tutaj można by uelastycznić kod, by można było generować dla różnych wartości c [c jest liczbą zespoloną], bo w zależności od nich zmienia sie całkowicie kształt fraktala. Wiem, wiem, generowanie trwa długo ale nie przerywaj działania skryptu :)

click to draw me [and wait]..

Ponieważ algorytm operuje na liczbach zespolonych musimy wykorzystać dodawanie, mnożenie i wartość bezwzględną dla liczb zespolonych, stąd kod trochę pogmatwany i na pierwszy rzut oka niezgodny z algorytmem z wikipedii. Kod:

// Julia fractal
function drawJulia(canvasID){
    // get canvas to be able to draw into it
    var canvas = document.getElementById(canvasID)
    var ctx = canvas.getContext("2d")

    var c_r = -0.73
    var c_i = 0.19

    // z= a+bi
    for(var a=-2; a<2; a+=0.01){
        for(var b=-2; b<2; b+=0.01){
            var za = a
            var zb = b
            for(var n=0; n<60; n++){
               var za_temp = za*za -zb*zb
               zb= 2*za*zb
               za = za_temp
               // add c constant
               za+=c_r
               zb+=c_i
            }
            if( Math.sqrt(za*za + zb*zb) <2 ){
                var zoom = 100
                dot(ctx, zoom*a+width/2, zoom*b+height/2, 1)
            }
        }
    }
}

I na koniec Zbiór Cantora Tutaj taka ciekawostka, kiedyś chciałem policzyć długość wszystkich odcinków, napisałem nawet stosowny program ale się wywalał. Dopiero potem zorientowałem się iż ta suma jest nieskończona!

click to draw me [and wait]..

Tym razem funkcja jest nieco inna, bo rekurencyjna. Nie ma sensu dawać większego poziomu rekurencji bo mniejszych elementów i tak nie widać. BTW a kiedyś napisałem wie funkcje rekurencyjne które się na wzajem wywoływały [ofc bez wątków].. ale nie działało dobrze.. Chyba się nad tym zastanowię i napisze kiedyś coś takiego dla sportu.. A może czytelnik takie coś napisał? Kod funkcji krzywych Cantora:

// Cantor fractal
function drawCantor(canvasID, level, xa, xb){
    // get canvas to be able to draw into it
    var canvas = document.getElementById(canvasID)
    var ctx = canvas.getContext("2d")

    var level_max = 10;
    if(level < level_max){
        // put bar
        var bar_height = 3
        for(var i=xa; i<xb; i++){
            var h = level * 6
            for(var j=h; j!=h+bar_height; j++){
                dot(ctx, i, j+100, 1)
            }
        }
        // invoke himself to put 2 nex bars
        drawCantor(canvasID, level+1, xa, xa+ (xb-xa)/3)
        drawCantor(canvasID, level+1, xb - (xb-xa)/3, xb)
    }
}

Testowa paczka, tak to pisałem, u siebie na dysku, podglądając co chwilę jak działa, a gdy było ok, napisałem posta i połączyłem to razem :) No więc jak ktoś chce to badać to niech skopiuje poniższy kod do pustej strony na swoim dysku i sprawdza Firefoxem i edytuje na bieżąco [mówię rzeczy oczywiste? hehe:D]

<script type="text/Javascript">
var width = 400,
    height = 400,
    dotColor = '#eee';

// put pixel on the canvas, ctx is canvas handler
function dot(ctx, x, y, pixSize) {
    ctx.save();
    ctx.fillStyle = dotColor;
    ctx.fillRect(x, y, pixSize, pixSize);
    ctx.restore();
}

// fern fractal
function drawFern(canvasID){
    // get canvas to be able to draw into it
    var canvas = document.getElementById(canvasID)
    var ctx = canvas.getContext("2d")

    var max_step=20000
    var x=0
    var y = 0

    for(var i=0; i<max_step; i++){
        var r = Math.random()
        if(r <= 0.01){
            x = 0
            y = 0.16*y

        }
        else if(r <= 0.08){
            x = 0.2*x-0.26*y;
            y = 0.23*x+0.22*y+1.6;
        }
        else if(r <= 0.15){
            x = -0.15*x+0.28*y;
            y = 0.26*x+0.24*y+0.44;
        }
        else {
            x = 0.85*x+0.04*y;
            y = -0.04*x+ 0.85*y+1.6;
        }
        // scale fractal to best fit to the canvas object
        var zoom = 37
        x_print = width - (x*zoom +width/2)
        y_print = height - (y*zoom)
        dot(ctx, x_print, y_print, 1)
    }
}

// logistic fractal
function drawLogistic(canvasID){
    // get canvas to be able to draw into it
    var canvas = document.getElementById(canvasID)
    var ctx = canvas.getContext("2d")

    var a_r = 2.95,
        ra = 2.95,
        rb = 4.9,
        step = 0.0001,
        a_x = 0.9;
    for(a_r = ra; a_r < rb; a_r += step*(rb-ra) ){
        for(var i=0; i<90*a_r; i++){
            a_x = a_r * a_x * (1-a_x);
            if(i>80*a_r)// here i say that 80=Infinity :)
                dot(ctx, width*(a_r-ra), height*a_x, 0.5)
        }
    }
}

// Julia fractal
function drawJulia(canvasID){
    // get canvas to be able to draw into it
    var canvas = document.getElementById(canvasID)
    var ctx = canvas.getContext("2d")

    var c_r = -0.73
    var c_i = 0.19

    // z= a+bi
    for(var a=-2; a<2; a+=0.01){
        for(var b=-2; b<2; b+=0.01){
            var za = a
            var zb = b
            for(var n=0; n<60; n++){
               var za_temp = za*za -zb*zb
               zb= 2*za*zb
               za = za_temp
               // add c constant
               za+=c_r
               zb+=c_i
            }
            if( Math.sqrt(za*za + zb*zb) <2 ){
                var zoom = 100
                dot(ctx, zoom*a+width/2, zoom*b+height/2, 1)
            }
        }
    }
}

// Cantor fractal
function drawCantor(canvasID, level, xa, xb){
    // get canvas to be able to draw into it
    var canvas = document.getElementById(canvasID)
    var ctx = canvas.getContext("2d")

    var level_max = 10;
    if(level < level_max){
        // put bar
        var bar_height = 3
        for(var i=xa; i<xb; i++){
            var h = level * 6
            for(var j=h; j!=h+bar_height; j++){
                dot(ctx, i, j+100, 1)
            }
        }
        // invoke himself to put 2 nex bars
        drawCantor(canvasID, level+1, xa, xa+ (xb-xa)/3)
        drawCantor(canvasID, level+1, xb - (xb-xa)/3, xb)
    }
}

</script>

<b><a href="JavaScript:drawFern('canvasFern')"  style="font-size:120%;color:black;">click to draw me [and wait]..</a></b>
<canvas id="canvasFern" width="400" height="400" style="background:#6D7B8D;margin-top:5px;"></canvas>

<b><a href="JavaScript:drawLogistic('canvasLogistic')"  style="font-size:120%;color:black;">click to draw me [and wait, don't stop the script!]..</a></b>
<canvas id="canvasLogistic" width="400" height="400" style="background:#6D7B8D;margin-top:5px;"></canvas>

<b><a href="JavaScript:drawJulia('canvasJulia')"  style="font-size:120%;color:black;">click to draw me [and wait]..</a></b>
<canvas id="canvasJulia" width="400" height="400" style="background:#6D7B8D;margin-top:5px;"></canvas>

<b><a href="JavaScript:drawCantor('canvasCantor', 0, 0, 400)"  style="font-size:120%;color:black;">click to draw me [and wait]..</a></b>
<canvas id="canvasCantor" width="400" height="400" style="background:#6D7B8D;margin-top:5px;"></canvas>

Pingbacks

No pingbacks yet

Comments

avatar
czarodziej , 23.03.2008 18:18, reply
fajnie że o fraktalach choć rysowanie ich przy pomocy js? u mnie straaasznie mulilo :)

Leave your reply

Let me know what you think

Required. 30 chars of fewer.

Required.

captcha image