Tekenen met <canvas>: De fractal van Mandelbrot
Met de komst van HTML5 is ook de tag <canvas> beschikbaar gekomen. Daarmee kun je tekeningen maken in je webdocument. Dat kan met behulp van voorgedefinieerde vormen, zoals lijnen, cirkels, rechthoeken, veelhoeken, enz. Het kan ook pixel-voor-pixel. Dat maakt het mogelijk om zeer complexe figuren te tekenen zoals fractals. Op deze pagina wordt getoond hoe je een figuur pixel voor pixel opbouwt, aan de hand van de fractal van Mandelbrot.
We beginnen met een uitgewerkte applicatie:
- Er worden twee items besproken:
- De implementatie van het algoritme voor het tekenen van de figuur.
- De werking van de applicatie.
- Inspiratie voor dit item is ontstaan door een toevallig bezoek aan de site rosettacode.org.
Die site heeft tot doel om oplossingen voor (programmeer-)problemen te presenteren in zoveel mogelijk programmeertalen, om zo de overeenkomsten en verschillend tussen die talen te laten zien.
Op het moment dat dit geschreven wordt zijn er op rosettacode.org voor het schrijven van 'Hello World' oplossingen beschikbaar in 480(!) programmertalen, van diverse assemblers tot 3e generatietalen zoals Pascal, C, enz. - De HTML- en JavaScript-code die de basis vormt voor dit verhaal is afkomstig van rosettacode.org. Voor deze site is de code geschikt gemaakt voor toepassing in een website (het origineel is gemaakt om te draaien vanuit een browser console) en is er een applicatie omheen gebouwd.
- De naam "rosettacode" is ontleend aan de Steen
van Rosetta, een tegel waarop een tekst staat in drie schriften, te weten Koinè-Grieks, in demotisch schrift (Egypte)
en in hiërogliefen (ook Egypte). Dit heeft geleid tot het ontcijferen van de Egyptische hiërogliefen.
- De implementatie van het algoritme
- Er zijn drie JavaScript-functions: setupEnInit(), mandelbrot() en mandelIter().
setupEnInit() maakt het <canvas>-element.
- De function mandelbrot() laat voor elk beeldpunt van het <canvas> het iteratieproces uitvoeren door
mandelIter() en berekent vervolgens de RGB-waarde voor dat beeldpunt.
Deze function heeft vijf parameters: xmin, xmax, ymin, ymax en iterations.
• Het paar xmin en ymin zijn de coördinaten van de linker onderhoek. Default: (-2,-1).
• Het paar xmax en ymax zijn de coördinaten van de rechter bovenhoek. Default: (1,1).
• iterations is het maximale aantal iteratie-stappen dat de function mandelIter() mag uitvoeren. Default: 1000. - Als eerste worden er gegevens opgehaald over het <canvas>-element. Dit is een wijziging ten opzichte van de oorspronkelijke code. Om voor mij onduidelijke redenen lukt het niet via getElementById(). Daarom is getElementsByTagName('canvas') ingezet. Deze function geeft een lijst van elementen terug, waarvan 'ons' <canvas>-element de eerste is en dus het volgnummer [0] heeft.
- Om de werking van mandelbrot() te kunnen volgen zijn drie variabelen van belang: width, height en pix. De eerste twee definiëren de afmetingen (in pixels) van het canvas, de derde is een array die voor elk beeldpunt vier elementen heeft: Drie kleurcomponenten Rood, Groen en Blauw , en het alfa-kanaal voor de doorzichtigheid. Dit zijn getallen tussen 0 en 255. In dit voorbeeld heeft pix 600 × 400 × 4 = 960 000 elementen.
- Op alle afzonderlijke punten van het canvas wordt het iteratieproces uitgevoerd. Aan de hand van het resultaat wordt de
kleur berekend:
- Als het iteratieproces stopt doordat het maximale aantal iteratie-stappen is bereikt, geeft de function mandelIter() een waarde terug die één hoger is dan het maximale aantal iteratie-stappen. Als dat gebeurt worden de RGB-waarden van het betreffende beeldpunt op nul (= zwart) gezet.
- Als het iteratieproces stopt doordat het convergeert geeft mandelIter() het uitgevoerde aantal iteratie-stappen terug. Daarmee, en met het maximale aantal iteratie-stappen, wordt een getal c berekend, dat bepalend is voor de kleur die het betreffende pixel krijgt. Door de manier waarop dat gebeurt loopt het palet van donkerrood naar lichtrood, via oranje naar geel om te eindigen met wit.
- Als de RGB-waarden van het beeldpunt zijn bepaald, wordt de waarde van het alfa-kanaal op 255 gezet, wat staat voor volledig ondoorzichtig.
- Als de array pix geheel is gevuld wordt deze in één keer in het canvas gezet door putImageData().
- De function mandelIter() voert het werkelijke iteratieproces uit. Deze function heeft drie parameters: cx,
cy en maxIter.
Het paar cx en cy zijn de coördinaten (in pixels) binnen het <canvas>-element. maxIter is het maximale aantal iteratie-stappen dat de function mandelIter() mag uitvoeren.
- Als eerste worden er vijf variabelen gedeclareerd, die allemaal de waarde nul krijgen:
• x en y zijn de "bewegende" coördinaten, waarvan de som van de kwadraten niet groter mag worden dan 4.
• xx staat voor x2, yy staat voor y2 en xy staat voor x * y. - Een zesde variabele is i. Deze krijgt de waarde van maxIter en wordt bij elke iteratie-stap met 1 verlaagd. Als i de waarde nul bereikt stopt het iteratieproces gestopt en wordt het aantal uitgevoerde stappen teruggegeven. Dit aantal wordt door de function mandelbrot() gebruikt om de kleur van het betreffende punt van het canvas te berekenen.
- De voorwaarde voor het uitvoeren van de volgende iteratie-stap is op het eerste gezicht wat vreemd:
while (i-- && xx + yy <= 4)
• De eerste eis waar aan moet zijn voldaan is:i--
. Bij elke iteratie-stap wordt i verlaagd, tot nul wordt bereikt. Als deze conditie optreedt retourneert de function de waarde maxIter, die wordt omgezet in de kleur zwart.
In het criterium kunnen alleen condities voorkomen die true of false zijn. In JavaScript wordt elke waarde die gelijk is aan nul geïnterpreteerd als false. Elke andere waarde is true. Zolang i groter is dan nul is aan de eis voldaan.
• De tweede eis waar aan moet zijn voldaan is:xx + yy ≤ 4
. Bij elke iteratie-stap worden xx en yy opnieuw berekend. De parameters cx en cy spelen daarbij een rol. Hoe verder het punt (cx,cy) van de oorsprong is verwijderd, hoe sneller xx + yy > 4 wordt en het iteratieproces stopt. De function retourneert een waarde groter dan nul, die wordt omgerekend in een kleur, uitgezonderd zwart.
- De werking van de applicatie
- De applicatie is gemaakt met HTML, JavaScript en een beetje CSS.
- HTML
- De HTML waar de applicatie is gemaakt bestaat uit drie delen:
• Het formulier voor het veranderen van de rekenparameters.
• Het deel waar de fractal figuur wordt geplaatst.
• De toelichting.
- Het formulier is gemaakt met default tekstinvoer-velden. Je kunt er dus alles intypen. Er is een aparte validatie
nodig om te verzekeren dat er geen verkeerde informatie wordt aangeleverd. Dat wordt gedaan met JavaScript.
Er is geen gebruik gemaakt van type="number", omdat dit type invoerveld (nog) niet goed omgaat met gebroken getallen. - De submit-knop start de function parseAndProcessInput().
- De fractal figuur staat in een canvas-tag die wordt gemaakt door het JavaScript. Om het ding op het scherm te positioneren is het in een <p>-tag geplaatst met id="fractal" , waarmee het in het midden van de regel wordt gezet. Om het goed in de <p>-tag te kunnen 'mikken' zijn er twee <span>-tags ingevoegd die alleen een non-breaking space bevatten.
- Bij de fractal figuur horen de meldingen over het maximale aantal iteratie-stappen en de coördinaten van de linker onderhoek en de rechter bovenhoek. Die staan in een <div>-tag met klasse toelichting.
- De toelichting is een blok tekst die ook zit in een <div>-tag met klasse toelichting.
- CSS
- Het id #fractal, dat ook wordt gebruikt om de plot in het <canvas>-element te zetten, wordt gebruikt om de plot te centreren in de regel. Dat gaat via een <p>-tag. Dat is een lijn-element, dus er wordt text-align:center gebruikt.
- De klasse toelichting wordt alleen gebruikt in <div>-tags. Dat zijn blok-elementen, dus wordt de content
gecentreerd met margin:0 auto. toelichting is 600px breed, even breed als de <canvas>-tag.
- JavaScript
- Het JavaScript staat, samen met de functions mandelbrot() en mandelIter() voor het genereren van de figuur, in de file mandelbrot1.js.
- Voor de applicatie zijn er twee functions:
setupEnInit() en parseAndProcessInput().
º setupEnInit() maakt het <canvas>-element. Deze function wordt gestart zodra de pagina is geladen.
º parseAndProcessInput() wordt aangeroepen door setupEnInit() en als er op de submit-knop wordt geklikt. - parseAndProcessInput() haalt de gegevens op uit het formulier en controleert of de informatie zinnig zou kunnen zijn. Als een fout wordt gevonden wordt er een melding gegeven en wordt de invoer teruggezet op de default-waarden. Als de verhouding van de opgegeven breedte en hoogte niet gelijk is aan 3:2, wordt er een melding gegeven dat de plot vertekend is.
- Als er geen fouten worden gevonden wordt mandelbrot() aangeroepen voor het maken van een nieuwe figuur.
Gebruik:
- De code staat gedeeltelijk in de <HEAD> en gedeeltelijk in de <BODY>.
- De code van het voorbeeld vind je in de downloadfile. Behalve de achtergrond en de opmaak van de tekst is die gelijk aan wat er op deze site staat.
Downloaden:
Druk op de knop:
File: voorb621.zip, 3442 bytes.
Opmerking:
De Fractal van Mandelbrot is nauw verbonden met de Juliaset. Zie het item
Tekenen met <canvas>: De Juliaset fractal