Exporter (0) Imprimer
Développer tout

Rendu initial de l’ensemble de Mandelbrot à l’aide de la zone de dessin

S’appuyant sur les informations présentées dans Brève vue d’ensemble de l’ensemble de Mandelbrot, cette rubrique décrit une méthode de base pour effectuer un rendu de l’ensemble de Mandelbrot à l’aide de la zone de dessin.

Avant de présenter un algorithme pour dessiner l’ensemble de Mandelbrot, nous allons d’abord créer une « classe » JavaScript pour la manipulation des nombres complexes. Comme indiqué dans Brève vue d’ensemble de l’ensemble de Mandelbrot, les seules opérations nécessaires sont la capacité à mettre au carré, à additionner et à prendre la valeur absolue de nombres complexes. L’exemple Opérations complexes utiles montre une manière de procéder.

Opérations complexes utiles


<!DOCTYPE html>
<html>

<head>
  <meta content="text/html; charset=utf-8" http-equiv="Content-Type">
  <title>Useful Complex Operations</title>
  <style>
    body {
      text-align: center;
    }
  </style>
</head>

<body>
  <h1>Useful Complex Operations</h1>
  <script>
    function Complex(x, y) {
    // Constructs the complex number x + yi.
      this.x = x || 0; // Default to 0 if this parameter is undefined.
      this.y = y || 0;
    } // Complex
    
    Complex.prototype.toString = function() {
    // Returns a string representing this complex number in the form "x + yi".
      return this.y >= 0 ? this.x + " + " + this.y + "i" : this.x + " - " + (-this.y) + "i";
    } // toString
    
    Complex.prototype.modulus = function() {
    // Returns a real number equal to the absolute value of this complex number.
      return Math.sqrt(this.x*this.x + this.y*this.y);
    } // modulus
        
    Complex.prototype.add = function(z) {
    // Returns a complex number equal to the sum of the given complex number and this complex number.
      return new Complex(this.x + z.x, this.y + z.y);
    } // sum

    Complex.prototype.square = function() {
    // Returns a complex number equal to the square of this complex number.
      var x = this.x*this.x - this.y*this.y;
      var y = 2*this.x*this.y;
      
      return new Complex(x, y);
    } // square
    
    var z1 = new Complex(0.2, 0.4);
    var z2 = new Complex(-0.6, 0.5);
    var z3 = z1.add(z2.square()); // Square z2 and add z1 to it.
    
    alert(z3.toString()); // Print the complex result in x + yi form.
    alert(z3.modulus()); // Print absolute value of z3.
  </script>
</body>

</html>

Dans l’exemple Opérations complexes utiles, nous avons :

  • z1 = 0,2 + 0,4i
  • z2 = -0,6 + 0,5i
  • z3 = (z2)2 + z1 = (-0,6 + 0,5i)2 + (0,2 + 0,4i) = 0,31 - 0,2i
  • |z3| = z3.modulus() ≈ 0,3689

Avec la « classe » JavaScript complexe en place, nous présentons un algorithme simple pour dessiner l’ensemble de Mandelbrot :

  1. Divisez un carré de 4 x 4, centré à l’origine complexe, en un grand nombre de points de grille (non illustré) :

    Plan complexe 4 x 4

  2. Traitez chaque point de la grille comme une valeur c.
  3. Pour chaque valeur c de ce type, vérifiez si |zn| tend vers l’infini avec un nombre d’itérations raisonnable (inférieur à zn+1 = zn + c ; où z0 = 0).
  4. Si ce n’est pas le cas, colorez c en noir (le noir indique que c est un membre de l’ensemble de Mandelbrot). Cela suppose que nous dessinons l’ensemble de Mandelbrot sur un arrière-plan blanc.

La mise en œuvre de cet algorithme en JavaScript génère une image semblable à la suivante :

Capture d’écran de Mandelbrot 1

Dans cette image, les deux axes de coordonnées rouges ont une longueur de 4. Autrement dit, un cercle de rayon 2 centré à l’origine du plan complexe contiendrait exactement ces axes. Comme mentionné plus haut, l’ensemble de Mandelbrot réside entièrement dans un cercle de rayon 2, et ce fait est exploité dans le code utilisé pour créer cette image, comme indiqué ci-dessous :

Mandelbrot 1


<!DOCTYPE html>
<html>

<head>
  <meta content="text/html; charset=utf-8" http-equiv="Content-Type">
  <title>Mandelbrot 1</title>
  <style>
    body {
      text-align: center;
    }
    
    canvas {
      border: 1px black solid;
    }
  </style>
</head>

<body>
  <h1>Mandelbrot 1</h1>
  <p>This example demonstrates a basic algorithm for drawing the Mandelbrot set using complex plane coordinates.</p>    
  <canvas width="600" height="600">Canvas not supported - upgrade your browser</canvas>
  <script>
    var CPS = 2; // CPS stands for "complex plane square". That is, we are examining a 2*CPS by 2*CPS square region of the complex plane such that this square (or box) is centered at the complex plane's origin.
    var MAX_ITERATIONS = 300; // Increase to improve detection of complex c values that belong to the Mandelbrot set.
    var DELTA = 0.008; // Decreasing this value increases the number of "pixels" on the canvas, thereby increasing the size of the rendering but without losing image resolution.
    
    function Complex(x, y) {
    // Constructs the complex number x + yi. If any parameter is undefined, 0 is used instead.
      this.x = x || 0;
      this.y = y || 0;
    } // Complex
    
    Complex.prototype.toString = function() {
    // Returns a string representing this complex number in the form "x + yi".
      return this.y >= 0 ? this.x + " + " + this.y + "i" : this.x + " - " + (-this.y) + "i";
    } // toString
    
    Complex.prototype.modulus = function() {
    // Returns a real number equal to the absolute value of this complex number.
      return Math.sqrt(this.x*this.x + this.y*this.y);
    } // modulus
        
    Complex.prototype.add = function(z) {
    // Returns a complex number equal to the sum of the given complex number and this complex number.
      return new Complex(this.x + z.x, this.y + z.y);
    } // sum

    Complex.prototype.square = function() {
    // Returns a complex number equal to the square of this complex number.
      var x = this.x*this.x - this.y*this.y;
      var y = 2*this.x*this.y;
      
      return new Complex(x, y);
    } // square
    
    var globals = {}; // Store all would-be-global-variables in one handy global object.
    globals.canvas = document.getElementsByTagName('canvas')[0];
    globals.canvas.ctx = globals.canvas.getContext('2d');
    globals.canvas.ctx.fillStyle = "black";                   
    
    initializeCoordinateSystem();
    drawMandelbrotSet();
    drawCoordinateAxes();
    
    function initializeCoordinateSystem() {
      var ctx = globals.canvas.ctx;

      ctx.translate(globals.canvas.width / 2, globals.canvas.height / 2); // Move the canvas's coordinate system to the center of the canvas.
      ctx.scale(1/DELTA, -1/DELTA); // Flip the y-axis to produce a standard Cartesian coordinate system and scale the canvas coordinate system to match the region of the complex plane under consideration.
    } // initializeCoordinateSystem  
    
    function drawMandelbrotSet() {
      var ctx = globals.canvas.ctx;
                    
      for (var Re = -CPS; Re <= CPS; Re = Re + DELTA) { // Represents the Re-axis. Re represents the real part of a complex c value.
        next_c_value: // "continue next_c_value;" is equivalent to an old school GOTO statement (which can be very handy in deeply nested loops).
        for (var Im = -CPS; Im <= CPS; Im = Im + DELTA) { // Represents the Im-axis. Im represents the imaginary part of a complex c value.
          var z = new Complex(0, 0); // Represents Zo (where "o" indicates subscript 0).
          var c = new Complex(Re, Im); // Represents a complex c value, which either does or does not belong to the Mandelbrot set, as determined in the next FOR loop.
          
          for (var iterationCount = 1; iterationCount <= MAX_ITERATIONS; iterationCount++) {
            z = c.add( z.square() ); // Performs Zn+1 = (Zn)^2 + c          
            if (z.modulus() > 2) {
              continue next_c_value; // The complex c value is not part of the Mandelbrot set, so immediately check the next one.
            } // if
          } // for
          
          // Assert: z.modulus() <= 2, therefore the complex c value is probably a member of the Mandelbrot set - increase MAX_ITERATIONS to improve this determination.

          ctx.fillRect(Re, Im, DELTA, DELTA); // This c value is probably part of the Mandelbrot set, so color this pixel black. A "pixel" for the canvas is a DELTA x DELTA black square.
        } // for
      } // for
    } // drawMandelbrotSet
    
    function drawCoordinateAxes() {
    /* 
      Draws coordinate axes that are exactly as long as the (square) complex plane region under consideration.
    */
      var ctx = globals.canvas.ctx;
      
      ctx.lineWidth = DELTA;
      ctx.strokeStyle = "red";
      
      // Draw the x-axis:
      ctx.beginPath();
      ctx.moveTo(CPS, 0);
      ctx.lineTo(-CPS, 0);
      ctx.stroke();
      
      // Draw the y-axis:
      ctx.beginPath();
      ctx.moveTo(0, CPS);
      ctx.lineTo(0, -CPS);
      ctx.stroke();      
    } // drawCoordinateAxes
  </script>
</body>

</html>

Les principales fonctions à examiner sont initializeCoordinateSystem et drawMandelbrotSet.

initializeCoordinateSystem

Comme son nom l’indique, initializeCoordinateSystem initialise le système de coordonnées utilisé dans la zone de dessin de 600 x 600 pixels. Par défaut, le point (0, 0) est toujours dans le coin supérieur gauche de la zone de dessin. Pour utiliser notre système de coordonnées cartésien familier, nous devons traduire (translate) cette origine au centre de la zone de dessin comme suit :


ctx.translate(globals.canvas.width / 2, globals.canvas.height / 2);

L’origine du système de coordonnées est maintenant centrée, mais l’axe y positif pointe vers le bas (décroissant) alors qu’il devrait pointer vers le haut (croissant). Pour corriger ce problème, nous pouvons utiliser la méthode scale comme suit :


ctx.scale(1, -1);

L’axe y est alors retourné (tandis que l’axe x demeure inchangé), ce qui donne un système de coordonnées cartésien traditionnel.

drawMandelbrotSet

La méthode scale peut également être utilisée pour augmenter ou réduire la taille d’une image rendue (plus le facteur de mise à l’échelle est élevé, plus l’image affichée est grande). Nous utilisons cette méthode dans l’exemple de code comme suit :


ctx.scale(1/DELTA, -1/DELTA);

Si DELTA est petit (disons 0,008) et représente également la taille d’un pixel de la zone de dessin, une image normalement minuscule est agrandie de manière significative car ctx.scale(1/DELTA, -1/DELTA) devient ctx.scale(125, -125). Ceci est apparent lorsque l’on examine drawMandelbrotSet :

drawMandelbrotSet


function drawMandelbrotSet() {
  var ctx = globals.canvas.ctx;
        
  for (var Re = -CPS; Re <= CPS; Re = Re + DELTA) { // Represents the Re-axis. Re represents the real part of a complex c value.
    next_c_value: // "continue next_c_value;" is equivalent to an old school GOTO statement (which can be very handy in deeply nested loops).
    for (var Im = -CPS; Im <= CPS; Im = Im + DELTA) { // Represents the Im-axis. Im represents the imaginary part of a complex c value.
      var z = new Complex(0, 0); // Represents Zo (where "o" indicates subscript 0).
      var c = new Complex(Re, Im); // Represents a complex c value, which either does or does not belong to the Mandelbrot set, as determined in the next FOR loop.
      
      for (var iterationCount = 1; iterationCount <= MAX_ITERATIONS; iterationCount++) {
        z = c.add( z.square() ); // Performs Zn+1 = (Zn)^2 + c          
        if (z.modulus() > 2) {
          continue next_c_value; // The complex c value is not part of the Mandelbrot set, so immediately check the next one.
        } // if
      } // for
      
      // Assert: z.modulus() <= 2, therefore the complex c value is probably a member of the Mandelbrot set - increase MAX_ITERATIONS to improve this determination.

      ctx.fillRect(Re, Im, DELTA, DELTA); // This c value is probably part of the Mandelbrot set, so color this pixel black. A "pixel" for the canvas is a DELTA x DELTA black square.
    } // for
  } // for
} // drawMandelbrotSet

Comme CPS est égal à 2, nous examinons chaque point de la grille dans le carré suivant (les points de la grille ne sont pas affichés) :

Plan complexe 4 x 4

Le nombre de points de la grille dans ce carré est déterminé par DELTA. Comme DELTA diminue, le nombre de points de la grille augmente :

for (var Re = -CPS; Re <= CPS; Re = Re + DELTA) {
  …
  for (var Im = -CPS; Im <= CPS; Im = Im + DELTA) { 
    …
  }
}

Nous définissons ensuite z0 à 0 + 0i = 0 comme nécessaire et nous créons c en fonction du point de la grille (Re, Im) que nous examinons actuellement :


var z = new Complex(0, 0);
var c = new Complex(Re, Im);

Nous pouvons maintenant déterminer si c est (plus que probablement) dans l’ensemble de Mandelbrot en calculant |zn| (avec un nombre d’itérations raisonnable) et en vérifiant si |zn| est supérieur à 2 :


for (var iterationCount = 1; iterationCount <= MAX_ITERATIONS; iterationCount++) {
  z = c.add( z.square() ); // Performs Zn+1 = (Zn)^2 + c          
  if (z.modulus() > 2) {
    continue next_c_value; // The complex c value is not part of the Mandelbrot set, so immediately check the next one.
  } // if
} // for

Pourquoi |zn| > 2 ? Car, comme il est décrit dans Propriétés de base, si la valeur absolue de zn devient supérieur à 2, la séquence tendra toujours vers l’infini (indiquant que c ne fait pas partie de l’ensemble de Mandelbrot).

Si, après MAX_ITERATIONS, la valeur absolue de zn est inférieure ou égale à 2 (z.modulus() <= 2), il est plus que probable que la valeur c associée à zn est un membre de l’ensemble de Mandelbrot. Par conséquent, nous colorons le point c en noir dans la grille :


ctx.fillRect(cx, cy, DELTA, DELTA);

Pour finir, nous dessinons deux axes de coordonnées en rouge en appelant drawCoordinateAxes.

L’un des principaux problèmes liés à Mandelbrot 1 est l’impossibilité de zoomer pour examiner les détails les plus fins de l’ensemble de Mandelbrot. Afin de disposer de cette fonctionnalité nécessaire, nous devons d’abord déterminer comment mapper un point sur la zone de dessin (en pixels) au plan complexe, ce que nous allons étudier dans Mappage de coordonnées d’écran au plan complexe.

Rubriques connexes

Mappage de coordonnées d’écran au plan complexe

 

 

Afficher:
© 2014 Microsoft