AccueilđŸ‡«đŸ‡·Chercher

Algorithme de tracé de segment de Bresenham

L’algorithme de tracĂ© de segment de Bresenham est un algorithme dĂ©veloppĂ© par Jack E. Bresenham en , alors qu’il travaillait dans un laboratoire informatique d’IBM et cherchait Ă  piloter un traceur attachĂ© Ă  une console texte. Cet algorithme a Ă©tĂ© prĂ©sentĂ© Ă  la convention de l’ACM en 1963, puis publiĂ© en 1965 dans la revue IBM Systems Journal.

Illustration de l'algorithme de Bresenham
Illustration de l'algorithme de Bresenham

L’algorithme dĂ©termine quels sont les points d’un plan discret qui doivent ĂȘtre tracĂ©s afin de former une approximation de segment de droite entre deux points donnĂ©s. Cet algorithme est souvent utilisĂ© pour dessiner des segments de droites sur l’écran d’un ordinateur ou une image calculĂ©e pour l’impression. Il est considĂ©rĂ© comme l’un des premiers algorithmes dĂ©couverts dans le domaine de la synthĂšse d'image [1].

Utilisations

Le principe du calcul est largement documentĂ© et a depuis Ă©tĂ© repris pour tracer de façon incrĂ©mentale n’importe quelle courbe conique (cercle, ellipse, arc, parabole, hyperbole) ou courbes de BĂ©zier grĂące aux propriĂ©tĂ©s de leur fonction polynomiale de dĂ©finition, dont les dĂ©rivĂ©es permettent de calculer les orientations de segments Ă©lĂ©mentaires avec de simples opĂ©rations entiĂšres. On peut mĂȘme l’adapter Ă  l’approximation de courbes dont on ne connaĂźt qu’un dĂ©veloppement limitĂ© (dont on ne prendra que les premiers termes de faible degrĂ©), utilisable avec une bonne prĂ©cision sur un domaine suffisant par rapport Ă  la rĂ©solution (sinusoĂŻdes, exponentielles, puissances non entiĂšres).

L’algorithme est Ă©galement facilement adaptable au calcul de courbes et surfaces dans un espace discret de plus de 2 dimensions (notamment pour le pilotage de machines-outils). MĂȘme en deux dimensions seulement, on peut discrĂ©tiser avec cet algorithme une courbe avec une fonction de lissage prenant en compte l’erreur commise entre deux points candidats afin d’ajuster leur couleur, l’erreur incrĂ©mentale Ă©tant convertible en coefficient de transparence, ce qui permet de conserver la graisse (Ă©paisseur visuelle) d’une courbe tout en limitant l’effet d’escalier (crĂ©nelage).

Cet algorithme intervient aussi dans le lissage de rendus de textures 2D appliquĂ©es sur des surfaces d’une scĂšne 3D oĂč la texture subit des rĂ©ductions ou agrandissements. On l’emploie aussi pour le lissage d’agrandissements photographiques, ou pour l’interpolation de couleurs intermĂ©diaires sur une Ă©chelle discrĂšte.

Explication de l’algorithme de base dans le premier octant

Illustration du rĂ©sultat de l’algorithme de tracĂ© de segment de Bresenham.

La ligne est tracĂ©e entre deux points (x1, y1) et (x2, y2), oĂč chaque paire indique la colonne et la rangĂ©e, respectivement, croissant vers la droite et le bas. On supposera au dĂ©part que notre segment descend vers le bas et la droite, et que la distance horizontale x2–x1 excĂšde la distance verticale y2–y1 (c’est-Ă -dire que le segment a une pente comprise entre 0 et -1). Notre but est, pour chaque colonne x entre x1 et x2, d’identifier la rangĂ©e y dans cette colonne qui est la plus proche du segment idĂ©al et de tracer un pixel en (x, y).

Détermination des ordonnées

Il s'agit maintenant de dĂ©terminer quel pixel est le plus proche de la droite pour une colonne donnĂ©e. La formule gĂ©nĂ©rale d’une droite entre les deux points est donnĂ©e par :

. (1)

Puisque la colonne est connue, la rangĂ©e du pixel le plus proche de l’ordonnĂ©e exacte du point d’abscisse sur la droite est donnĂ©e en arrondissant cette ordonnĂ©e y Ă  l’entier le plus proche :

. (2)

La valeur 0,5 dans le membre de droite est une approximation des coordonnées (le point est au milieu du carré).

Cependant, le calcul explicite de cette valeur pour chaque colonne est coĂ»teux. Or commence en , et que chaque fois qu'on ajoute 1 Ă  l'abscisse, on ajoute la valeur constante Ă  la valeur de l’ordonnĂ©e y du point de la droite correspondant. Cette valeur est la pente de la droite, et en vertu de notre hypothĂšse initiale, elle est comprise entre 0 et -1. AprĂšs l’arrondi, pour chaque colonne , on utilise donc soit la valeur prĂ©cĂ©dente (ordonnĂ©e du pixel d'abscisse ), soit cette valeur augmentĂ©e de 1.

On peut dĂ©cider laquelle de ces deux valeurs prendre en conservant une valeur d’erreur qui reprĂ©sente la distance verticale entre la valeur courante et la valeur y exacte pour la droite Ă  l’abscisse courante. Au dĂ©part cette valeur d’erreur e est nulle et chaque fois qu'on incrĂ©mente , on augmente la valeur d’erreur par la valeur de pente ci-dessus. Chaque fois que l’erreur dĂ©passe 0,5, la droite est devenue plus proche de la valeur suivante, aussi on ajoute 1 Ă  en retranchant simultanĂ©ment 1,0 Ă  l’erreur e.

La procĂ©dure ressemble Ă  ceci, en supposant que tracerPixel(x, y) est une primitive graphique traçant le pixel de rangĂ©e x et de colonne y ; exprimĂ© en pseudo-code, l’algorithme de base est :

procédure tracerSegment(entier x1, entier y1, entier x2, entier y2) est
  déclarer entier x, y, dx, dy ;
  dĂ©clarer rationnel e, e(1,0), e(0,1) ;  // valeur d’erreur et incrĂ©ments
  dy ← y2 - y1 ;
  dx ← x2 - x1 ;
  y ← y1 ;  // rangĂ©e initiale
  e ← 0,0 ;  // valeur d’erreur initiale
  e(1,0) ← dy / dx ;
  e(0,1) ← -1.0 ;
  pour x variant de x1 jusqu’à x2 par incrĂ©ment de 1 faire
    tracerPixel(x, y) ;
    si (e ←  e + e(1,0)) ≄ 0,5 alors  // erreur pour le pixel suivant de mĂȘme rangĂ©e
      y ←  y + 1 ;  // choisir plutĂŽt le pixel suivant dans la rangĂ©e supĂ©rieure
      e ←  e + e(0,1) ;  // ajuste l’erreur commise dans cette nouvelle rangĂ©e
    fin si ;
  fin pour ;
fin procédure ;

AmĂ©lioration de l’algorithme pour le calcul avec des entiers

Le problĂšme avec cet algorithme simple est que les microprocesseurs d’ordinateur sont relativement lents dans le calcul sur des nombres en virgule flottante (et la reprĂ©sentation suggĂ©rĂ©e ci-dessus sous forme de nombres rationnels pour e et e(1,0) est nettement plus complexe et non nativement prise en charge par les processeurs ce qui augmente le nombre d’instructions pour travailler sur de tels nombres) ; de plus, les erreurs d’approximation du nombre flottant e(1,0) s’accumulent Ă  chaque addition de e(1,0) dans e. Travailler avec uniquement des entiers permettrait un calcul Ă  la fois plus exact et plus rapide.

La premiĂšre astuce est de remarquer d’abord qu’on peut remplacer e par e–0,5, ce qui permet de ne tester que le signe de la valeur d’erreur au lieu de comparer deux rationnels, le test de proximitĂ© par rapport Ă  la droite exacte revient alors Ă  savoir lequel des deux pixels candidats se situe en dessous de la droite exacte parallĂšle dont les ordonnĂ©es sont augmentĂ©es de 0,5, et de remplacer l’arrondi de la formule (1) ci-dessus Ă  l’entier le plus proche par un arrondi Ă  l’entier Ă©gal ou infĂ©rieur avec cette nouvelle droite, ce qui ne change effectivement pas la formule (2) ci-dessus, mais e reprĂ©sentera l’erreur commise lors de l'approximation de cette seconde droite par les pixels tracĂ©s :

procédure tracerSegment(entier x1, entier y1, entier x2, entier y2) est
  déclarer entier x, y, dx, dy ;
  dĂ©clarer rationnel e, e(1,0), e(0,1) ;  // valeur d’erreur et incrĂ©ments
  dy ← y2 - y1 ;
  dx ← x2 - x1 ;
  y ← y1 ;  // rangĂ©e initiale
  e ← -0,5 ;  // valeur d’erreur initiale
  e(1,0) ← dy / dx ;
  e(0,1) ← -1.0 ;
  pour x variant de x1 jusqu’à x2 par incrĂ©ment de 1 faire
    tracerPixel(x, y) ;
    si (e ← e + e(1,0)) ≄ 0 alors  // erreur pour le pixel suivant de mĂȘme rangĂ©e
      y ← y + 1 ;  // choisir plutĂŽt le pixel suivant dans la rangĂ©e supĂ©rieure
      e ← e + e(0,1) ;  // ajuste l’erreur commise dans cette nouvelle rangĂ©e
    fin si ;
  fin pour ;
fin procédure ;

Ensuite en multipliant tous les rationnels ci-dessus par dx, le calcul ne demande plus d’incrĂ©ments rationnels (ce qui Ă©limine l'accumulation d'erreurs d’approximation des flottants). Cependant la valeur initiale de e= –0,5×dx n’est pas encore entiĂšre, mĂȘme si ses incrĂ©ments sont entiers.

procédure tracerSegment(entier x1, entier y1, entier x2, entier y2) est
  déclarer entier x, y, dx, dy ;
  dĂ©clarer rationnel e ; // valeur d’erreur
  déclarer entier e(1,0), e(0,1) ;  // incréments
  dy ← y2 - y1 ;
  dx ← x2 - x1 ;
  y ← y1 ;  // rangĂ©e initiale
  e ← -0,5 × dx ;  // valeur d’erreur initiale
  e(1,0) ←  dy ;
  e(0,1) ← -dx ;
  pour x variant de x1 jusqu’à x2 par incrĂ©ment de 1 faire
    tracerPixel(x, y) ;
    si (e ← e + e(1,0)) ≄ 0 alors  // erreur pour le pixel suivant de mĂȘme rangĂ©e
      y ← y + 1 ;  // choisir plutĂŽt le pixel suivant dans la rangĂ©e supĂ©rieure
      e ← e + e(0,1) ;  // ajuste l’erreur commise dans cette nouvelle rangĂ©e
    fin si ;
  fin pour ;
fin procédure ;

Toutefois en doublant e (et les valeurs de ses incrĂ©ments), cela ne change rien au test de son signe : e reprĂ©sentera alors la distance par rapport Ă  la droite exacte d’ordonnĂ©es augmentĂ©es de 0,5, cette distance Ă©tant multipliĂ©e par le facteur constant positif 2×dy (ce qui ne change pas le signe de la valeur d’erreur testĂ©e). La valeur de pente utilisĂ©e comme incrĂ©ment de e Ă©tant aussi multipliĂ©e par le mĂȘme facteur devient simplement 2×dy, sa valeur initiale devient –dx, le second dĂ©crĂ©ment conditionnel devient 2×dx (que l’on peut aussi prĂ©calculer). L’algorithme devient alors :

procédure tracerSegment(entier x1, entier y1, entier x2, entier y2) est
  déclarer entier x, y, dx, dy ;
  dĂ©clarer entier e ; // valeur d’erreur
  déclarer entier e(1,0), e(0,1) ;  // incréments
  dy ← y2 - y1 ;
  dx ← x2 - x1 ;
  y ← y1 ;  // rangĂ©e initiale
  e ← -dx ;  // valeur d’erreur initiale
  e(1,0) ←  dy × 2 ;
  e(0,1) ← -dx × 2;
  pour x variant de x1 jusqu’à x2 par incrĂ©ment  de 1 faire
    tracerPixel(x, y) ;
    si (e ← e + e(1,0)) ≄ 0 alors  // erreur pour le pixel suivant de mĂȘme rangĂ©e
      y ← y + 1 ;  // choisir plutĂŽt le pixel suivant dans la rangĂ©e supĂ©rieure
      e ← e + e(0,1) ;  // ajuste l’erreur commise dans cette nouvelle rangĂ©e
    fin si ;
  fin pour ;
fin procédure ;
Tracé de segment avec l'algorithme de Bresenham (exemple animé, avec de plus en plus de carrés)
Un exemple de tracĂ© de segments avec l'algorithme de Bresenham, en une animation avec de plus en plus de carrĂ©s et toujours la mĂȘme pente.

RĂ©duction des variables et simplifications

On pourra enfin changer le signe de e en testant le signe opposĂ©, puis rĂ©duire le nombre de variables, en constatant que x1 et y1 ci-dessus ne sont plus utilisĂ©s dĂšs que l’erreur initiale et les incrĂ©ments sont calculĂ©s ; il suffit de changer aussi le signe des incrĂ©ments et dĂ©crĂ©ments :

procédure tracerSegment(entier x1, entier y1, entier x2, entier y2) est
  déclarer entier dx, dy ;
  dĂ©clarer entier e ; // valeur d’erreur
  e  ← x2 - x1 ;        // -e(0,1)
  dx ← e × 2 ;          // -e(0,1)
  dy ← (y2 - y1) × 2 ;  // e(1,0)
  tant que x1 ≀ x2 faire
    tracerPixel(x1, y1) ;
    x1 ← x1 + 1 ;  // colonne du pixel suivant
    si (e ← e - dy) ≀ 0 alors  // erreur pour le pixel suivant de mĂȘme rangĂ©e
      y1 ← y1 + 1 ;  // choisir plutĂŽt le pixel suivant dans la rangĂ©e supĂ©rieure
      e ← e + dx ;  // ajuste l’erreur commise dans cette nouvelle rangĂ©e
    fin si ;
  fin faire ;
  // Le pixel final (x2, y2) n’est pas tracĂ©.
fin procédure ;

Cet algorithme est optimal et suffisant pour tracer tout vecteur horizontal diagonal ou oblique dans le premier octant, de colonnes et rangĂ©es croissantes. Si le langage de programmation le permet, les deux variables locales dĂ©clarĂ©es x et y peuvent ĂȘtre remplacĂ©es par rĂ©utilisation des variables x1 et y1 des paramĂštres. Ce cas est traitĂ© ci-dessus.

Toutefois il faut remarquer dans l’algorithme ci-dessus que le test du signe de e peut aussi bien inclure l’égalitĂ© avec zĂ©ro ou ne pas l’inclure. Cela correspond au fait que les deux pixels suivants candidats sont Ă©quidistants de la droite exacte. Si on choisit un dĂ©placement diagonal le deuxiĂšme point suivant sera toujours obtenu par un dĂ©placement horizontal ; si on choisit un dĂ©placement horizontal le deuxiĂšme point suivant sera toujours obtenu par un dĂ©placement diagonal. Si on inversait la direction du vecteur, les pixels choisis seraient inversĂ©s donc diffĂ©rents, et on devra en tenir compte si on souhaite un recouvrement exact des pixels de deux vecteurs obliques de sens opposĂ©s, lors de la gĂ©nĂ©ralisation de l’algorithme Ă  des vecteurs obliques de directions quelconques (ce cas ne peut pas se produire pour le tracĂ© de vecteurs horizontaux, verticaux ou diagonaux).

Algorithme général optimisé

La gĂ©nĂ©ralisation de l’algorithme de base au tracĂ© de vecteurs de direction quelconque est obtenue par simple symĂ©trie.

L’algorithme est ici dĂ©veloppĂ© et optimisĂ© dans chacun des huit octants. Toutefois, afin de s’assurer que les mĂȘmes pixels seront toujours tracĂ©s pour deux vecteurs identiques mais de direction opposĂ©e, on inversera les cas limites oĂč un dĂ©placement diagonal est Ă  Ă©galitĂ© avec un dĂ©placement droit, en choissant la diagonale quand le vecteur est orientĂ© vers la gauche (abscisses dĂ©croissantes) plutĂŽt que vers la droite (abscisses croissantes) comme dans le cas simplifiĂ© ci-dessus :


 procédure tracerSegment(entier x1, entier y1, entier x2, entier y2) est
  déclarer entier dx, dy;
  si (dx ← x2 - x1) ≠ 0 alors
    si dx > 0 alors
      si (dy ← y2 - y1) ≠ 0 alors
        si dy > 0 alors
          // vecteur oblique dans le 1er quadrant
          si dx ≄ dy alors
            // vecteur diagonal ou oblique proche de l’horizontale, dans le 1er octant
            déclarer entier e ;
            dx ← (e ← dx) × 2 ; dy ← dy × 2 ;  // e est positif
            boucle sans fin  // déplacements horizontaux
              tracePixel(x1, y1) ;
              interrompre boucle si (x1 ← x1 + 1) = x2 ;
              si (e ← e - dy) < 0 alors
                y1 ← y1 + 1 ;  // dĂ©placement diagonal
                e ← e + dx ;
              fin si ;
            fin boucle ;
          sinon
            // vecteur oblique proche de la verticale, dans le 2d octant
            déclarer entier e ;
            dy ← (e ← dy) × 2 ; dx ← dx × 2 ;  // e est positif
            boucle sans fin  // déplacements verticaux
              tracePixel(x1, y1) ;
              interrompre boucle si (y1 ← y1 + 1) = y2 ;
              si (e ← e - dx) < 0 alors
                x1 ← x1 + 1 ;  // dĂ©placement diagonal
                e ← e + dy ;
              fin si ;
            fin boucle ;
          fin si ;
        sinon  // dy < 0 (et dx > 0)
          // vecteur oblique dans le 4e quadrant
          si dx ≄ -dy alors
            // vecteur diagonal ou oblique proche de l’horizontale, dans le 8e octant
            déclarer entier e ;
            dx ← (e ← dx) × 2 ; dy ← dy × 2 ;  // e est positif
            boucle sans fin  // déplacements horizontaux
              tracePixel(x1, y1) ;
              interrompre boucle si (x1 ← x1 + 1) = x2 ;
              si (e ← e + dy) < 0 alors
                y1 ← y1 - 1 ;  // dĂ©placement diagonal
                e ← e + dx ;
              fin si ;
            fin boucle ;
          sinon  // vecteur oblique proche de la verticale, dans le 7e octant
            déclarer entier e ;
            dy ← (e ← dy) × 2 ; dx ← dx × 2 ;  // e est nĂ©gatif
            boucle sans fin  // déplacements verticaux
              tracePixel(x1, y1) ;
              interrompre boucle si (y1 ← y1 - 1) = y2 ;
              si (e ← e + dx) > 0 alors
                x1 ← x1 + 1 ;  // dĂ©placement diagonal
                e ← e + dy ;
              fin si ;
            fin boucle ;
          fin si ;
        fin si ;
      sinon  // dy = 0 (et dx > 0)
        // vecteur horizontal vers la droite
        répéter
          tracePixel(x1, y1) ;
        jusqu’à ce que (x1 ← x1 + 1) = x2 ;
      fin si ;
    sinon  // dx < 0
      si (dy ← y2 - y1) ≠ 0 alors
        si dy > 0 alors
          // vecteur oblique dans le 2d quadrant
          si -dx ≄ dy alors
            // vecteur diagonal ou oblique proche de l’horizontale, dans le 4e octant
            déclarer entier e ;
            dx ← (e ← dx) × 2 ; dy ← dy × 2 ;  // e est nĂ©gatif
            boucle sans fin  // déplacements horizontaux
              tracePixel(x1, y1) ;
              interrompre boucle si (x1 ← x1 - 1) = x2 ;
              si (e ← e + dy) ≄ 0 alors
                y1 ← y1 + 1 ;  // dĂ©placement diagonal
                e ← e + dx ;
              fin si ;
            fin boucle ;
          sinon
            // vecteur oblique proche de la verticale, dans le 3e octant
            déclarer entier e ;
            dy ← (e ← dy) × 2 ; dx ← dx × 2 ;  // e est positif
            boucle sans fin  // déplacements verticaux
              tracePixel(x1, y1) ;
              interrompre boucle si (y1 ← y1 + 1) = y2 ;
              si (e ← e + dx) ≀ 0 alors
                x1 ← x1 - 1 ;  // dĂ©placement diagonal
                e ← e + dy ;
              fin si ;
            fin boucle ;
          fin si ;
        sinon  // dy < 0 (et dx < 0)
          // vecteur oblique dans le 3e quadrant
          si dx ≀ dy alors
            // vecteur diagonal ou oblique proche de l’horizontale, dans le 5e octant
            déclarer entier e ;
            dx ← (e ← dx) × 2 ; dy ← dy × 2 ;  // e est nĂ©gatif
            boucle sans fin  // déplacements horizontaux
              tracePixel(x1, y1) ;
              interrompre boucle si (x1 ← x1 - 1) = x2 ;
              si (e ← e - dy) ≄ 0 alors
                y1 ← y1 - 1 ;  // dĂ©placement diagonal
                e ← e + dx ;
              fin si ;
            fin boucle ;
          sinon  // vecteur oblique proche de la verticale, dans le 6e octant
            déclarer entier e ;
            dy ← (e ← dy) × 2 ; dx ← dx × 2 ;  // e est nĂ©gatif
            boucle sans fin  // déplacements verticaux
              tracePixel(x1, y1) ;
              interrompre boucle si (y1 ← y1 - 1) = y2 ;
              si (e ← e - dx) ≄ 0 alors
                x1 ← x1 - 1 ;  // dĂ©placement diagonal
                e ← e + dy ;
              fin si ;
            fin boucle ;
          fin si ;
        fin si ;
      sinon  // dy = 0 (et dx < 0)
        // vecteur horizontal vers la gauche
        répéter
          tracePixel(x1, y1) ;
        jusqu’à ce que (x1 ← x1 - 1) = x2 ;
      fin si ;
    fin si ;
  sinon  // dx = 0
    si (dy ← y2 - y1) ≠ 0 alors
      si dy > 0 alors
        // vecteur vertical croissant
        répéter
          tracePixel(x1, y1) ;
        jusqu’à ce que (y1 ← y1 + 1) = y2 ;
      sinon  // dy < 0 (et dx = 0)
        // vecteur vertical décroissant
        répéter
          tracePixel(x1, y1) ;
        jusqu’à ce que (y1 ← y1 - 1) = y2 ;
      fin si ;
    fin si ;
  fin si ;
  // le pixel final (x2, y2) n’est pas tracĂ©.
fin procédure ;

Notes :

  • Ci-dessus, les affectations sont parfois regroupĂ©es au sein des expressions qui en rĂ©utilisent la valeur affectĂ©e. Si le langage utilisĂ© ne le permet pas, il suffit d'effectuer les affectations internes dans des instructions d’affectation prĂ©alables sĂ©parĂ©es, et de relire cette variable dans l’expression contenante.
  • Les primitives graphiques tracePixel(x1, y1) sont regroupĂ©es ci-dessus pour tracer dans la boucle interne des segments horizontaux (cas du premier octant ci-dessus) ou verticaux (second octant), et peuvent ĂȘtre regroupĂ©es en un seul appel (il suffit de compter le nombre de passages dans la boucle interne, et d’effectuer le tracĂ© de segment horizontal (ou vertical) en sortie de cette boucle.

Références

  1. James D. Foley, Introduction Ă  l'infographie, Paris, Addison-Wesley France, , 573 p. (ISBN 978-2-87908-058-1, BNF 37480856), p.76 Ă  84

Articles connexes

Cet article est issu de wikipedia. Text licence: CC BY-SA 4.0, Des conditions supplĂ©mentaires peuvent s’appliquer aux fichiers multimĂ©dias.