Bresenham-Algorithmus

aus Wikipedia, der freien Enzyklopädie
Wechseln zu: Navigation, Suche

Der Bresenham-Algorithmus ist ein Algorithmus in der Computergrafik zum Zeichnen (Rastern) von Geraden oder Kreisen auf Rasteranzeigen. Für Linienalgorithmen gibt es einen eigenen Übersichtsartikel, hier wird mehr die konkrete Implementierung erläutert.

Der Algorithmus wurde 1962 von Jack Bresenham, damals Programmierer bei IBM, entwickelt.[1] Das Besondere an seinem Algorithmus ist, dass er Rundungsfehler, die durch die Diskretisierung von kontinuierlichen Koordinaten entstehen, minimiert, und gleichzeitig einfach implementierbar ist, mit der Addition von ganzen Zahlen als komplexeste Operation, und somit ohne Multiplikation, Division und Gleitkommazahlen auskommt.

Durch eine geringfügige Erweiterung lässt sich der ursprüngliche Algorithmus, der für Geraden entworfen wurde, auch für die Rasterung von Kreisen verwenden. Sogar die Quadratterme, die beim Kreis vorkommen, lassen sich rekursiv ohne jede Multiplikation aus dem jeweils vorhergehenden Term ableiten nach (n+1)^2 = n^2 + 2 \cdot n + 1, wobei der Term 2 \cdot n nicht als Multiplikation zählt, da er in Hardware bzw. auf Assemblerebene als einfache Shift-Operation implementiert wird und der Term n^2 im Endeffekt ganz vermieden werden kann.

Aufgrund obiger Eigenschaften ist die Bedeutung des Bresenham-Algorithmus bis heute ungebrochen, und er kommt unter anderem in Plottern, in den Grafikchips moderner Grafikkarten und in vielen Grafikbibliotheken zur Verwendung. Dabei ist er so einfach, dass er nicht nur in der Firmware solcher Geräte verwendet wird, sondern in Grafikchips direkt in Hardware implementiert werden kann.

Der Name „Bresenham“ wird heute zudem für eine ganze Familie von Algorithmen verwendet, die eigentlich von Anderen später entwickelt wurden, jedoch in der Nachfolge von Bresenham und mit einem verwandten Ansatz. Siehe weiterführende Literaturhinweise unten.

Ansatz[Bearbeiten]

Rastern einer Linie nach dem Bresenham-Verfahren, unten der Verlauf der Fehlervariablen

Die hier vorgestellte Variante ist ein sehr praxisnaher Ansatz und wurde zuerst von Pitteway[2] veröffentlicht und von van Aken[3] verifiziert. Weiter unten wird die Variante mit der originalen Formulierung von Bresenham verglichen und gezeigt, dass die Lösungen äquivalent sind.

Zum Verständnis des Algorithmus beschränkt man sich auf den ersten Oktanten, also eine Linie mit einer Steigung zwischen 0 und 1 von (x_\mathrm{start}, y_\mathrm{start}) nach (x_\mathrm{end}, y_\mathrm{end}). Seien dx=x_\mathrm{end}-x_\mathrm{start} und dy=y_\mathrm{end}-y_\mathrm{start} mit 0<dy\leq dx. Für andere Oktanten muss man später Fallunterscheidungen über Vorzeichen von dx und dy und die Rollenvertauschung von x und y treffen.

Der Algorithmus läuft dann so, dass man in der „schnellen“ Richtung (hier die positive x-Richtung) immer einen Schritt macht und je nach Steigung hin und wieder zusätzlich einen Schritt in der „langsameren“ Richtung (hier y). Man benutzt dabei eine Fehlervariable, die bei einem Schritt in x-Richtung den (kleineren) Wert dy subtrahiert bekommt. Bei Unterschreitung des Nullwerts wird ein y-Schritt fällig und der (größere) Wert dx zur Fehlervariablen addiert. Diese wiederholten „Überkreuz“-Subtraktionen und -Additionen lösen die Division des Steigungsdreiecks in elementarere Rechenschritte auf.

Zusätzlich muss dieses Fehlerglied vorher sinnvoll initialisiert werden. Dazu betrachtet man den Fall von dy=1, wo man gern in der Mitte (nach der Hälfte von dx) den einen y-Schritt hätte. Also initialisiert man mit (dx/2). (Ob dabei zu einer ganzen Zahl auf- oder abgerundet wird, spielt kaum eine Rolle.)

Mathematisch gesehen wird die Geradengleichung

y=y_\mathrm{start} + (x - x_\mathrm{start}) \cdot \frac{dy}{dx}

aufgelöst in

0 = dx \cdot (y - y_\mathrm{start}) - dy \cdot (x-x_\mathrm{start})

und die Null links durch das Fehlerglied ersetzt. Ein Schritt um 1 in x-Richtung (Variable x) bewirkt eine Verminderung des Fehlerglieds um 1 \cdot dy. Wenn das Fehlerglied dabei unter Null gerät, wird es durch einen Schritt um 1 in y-Richtung (Variable y) um ein Mal dx erhöht, was nach der Voraussetzung dx>=dy das Fehlerglied auf jeden Fall wieder positiv macht, bzw. mindestens auf Null bringt.

Der originale Ansatz nach Bresenham (s. u.) geht übrigens mehr geometrisch vor, wodurch in seinen Iterationsformeln auf beiden Seiten (bis auf das Fehlerglied) ein zusätzlicher Faktor 2 mitgeführt wird und auch die Fehlergliedinitialisierung anders hergeleitet wird.

Einfache Implementierung[Bearbeiten]

Eine erste Implementierung ist nicht sehr elegant, demonstriert das Prinzip des Algorithmus aber sehr gut.

 REM Bresenham-Algorithmus für eine Linie im ersten Oktanten in Pseudo-Basic
 dx = xend-xstart
 dy = yend-ystart
 REM im ersten Oktanten muss 0 < dy <= dx sein
 
 REM Initialisierungen
 x = xstart
 y = ystart
 SETPIXEL x,y
 fehler = dx/2
 
 REM Pixelschleife: immer ein Schritt in schnelle Richtung, hin und wieder auch einer in langsame
 WHILE x < xend
    REM Schritt in schnelle Richtung
    x = x + 1
    fehler = fehler-dy
    IF fehler < 0 THEN
       REM Schritt in langsame Richtung
       y = y + 1
       fehler = fehler + dx
    END IF
    SETPIXEL x,y
 WEND

Vergleich mit der Original-Bresenham-Formulierung[Bearbeiten]

 REM Quasi-Bresenham-Algorithmus     REM Original-Bresenham
 dx = xend-xstart                    dx = xend-xstart
 dy = yend-ystart                    dy = yend-ystart
 
                                     d = 2*dy – dx
                                     DO = 2*dy
                                     dNO = 2*(dy - dx)
 
 x = xstart                          x = xstart
 y = ystart                          y = ystart
 SETPIXEL x,y                        SETPIXEL x,y
 fehler = dx/2                       fehler = d
 
 WHILE x < xend                      WHILE x < xend
    x = x + 1                           x = x + 1
    fehler = fehler-dy
    IF fehler < 0 THEN                  IF fehler <= 0 THEN
                                           fehler = fehler + DO
                                        ELSE
       y = y + 1                           y = y + 1
       fehler = fehler + dx                fehler = fehler + dNO
    END IF                              END IF
    SETPIXEL x,y                        SETPIXEL x,y
 WEND                                WEND

Abgesehen von der Anpassung an den verwendeten BASIC-Dialekt sind folgende Unterschiede bei der originalen Formulierung zu beachten:

  • Das Fehlerglied wird mit umgekehrtem Vorzeichen verwendet.
  • Das Fehlerglied wird auf sein Vorzeichen abgefragt, bevor es aktualisiert wird, dadurch wird die zusätzliche Initialisierung mit dem dy-Term notwendig.
  • Das Fehlerglied ist um den Faktor 2 erweitert, so dass bei der Initialisierung keine Division durch 2 stattfindet, dafür aber die Schrittvariablen für die Fehleraktualisierungen doppelt so groß sind.

Wenn man diese Unterschiede in der Formulierung berücksichtigt, stellt sich heraus, dass beide Formulierungen identisch arbeiten und somit äquivalent sind.

Elegantere Implementierungen[Bearbeiten]

Als eleganter formulierte Beispiele folgen als erstes der Quellcode eines BASIC-Programmes und anschließend eines Unterprogramms in C, welche die Fallunterscheidung in acht Oktanten nicht ausdrücklich vornehmen müssen.

Der Algorithmus soll für alle Oktanten gültig werden. Dabei müssen die Vorzeichen der Koordinatendistanzen und die eventuelle Vertauschung der Rollen von x und y berücksichtigt werden. Wenn man diese Fallunterscheidungen innerhalb der innersten Schleife treffen würde, also in hoher Anzahl, würde das die Geschwindigkeit der Berechnungen deutlich verringern. Eine effiziente Lösung versucht, all diese Fallunterscheidungen in der Initialisierungsphase des Verfahrens vor der inneren Hauptschleife abzuarbeiten, so dass innerhalb der inneren Schleife weiterhin nur die eine Abfrage für das Bresenham-Fehlerglied erfolgen muss.

Diese Formulierung führt (wie schon Stockton[4] indirekt vorschlug) diverse Abstraktionen ein: Zunächst wird der Schritt in die schnelle Richtung jetzt als Parallelschritt (parallel zu einer Koordinatenachse) angesehen, und wenn zusätzlich ein Schritt in die langsame Richtung erfolgt, wird das zu einem Diagonalschritt. Dann kann man in der Initialisierung Variablenwerte ermitteln, die für diese Fälle die Schrittweiten in den beiden Koordinatenrichtungen vorab festlegen und somit die Verallgemeinerung für die acht Oktanten erreichen. Beispielsweise ist bei einem Parallelschritt die Schrittweite in die dazu senkrechte Richtung eben Null. Zweitens rechnet man den Fehlerterm weiterhin wie im ersten Oktanten, mit Absolutbeträgen der Distanzen. In der innersten Schleife wird dann nicht mehr zuerst der Schritt in der schnellen Richtung ausgeführt, sondern als Erstes der Fehlerterm aktualisiert, und danach erst werden die Schrittweiten zu den bisherigen Koordinaten addiert, je nachdem, ob ein Parallel- oder ein Diagonalschritt erfolgen muss:

BASIC-Implementierung[Bearbeiten]

 REM Bresenham-Algorithmus für eine Linie in einem beliebigen Oktanten in Pseudo-Basic
 dx = xend-xstart
 dy = yend-ystart
 
 REM Initialisierungen
 adx = ABS(dx): ady = ABS(dy) ' Absolutbeträge Distanzen
 sdx = SGN(dx): sdy = SGN(dy) ' Signum Distanzen
 
 IF adx > ady THEN
   ' x ist schnelle Richtung
   pdx = sdx: pdy = 0   ' pd. ist Parallelschritt
   ddx = sdx: ddy = sdy ' dd. ist Diagonalschritt
   es  = ady: el  = adx ' Fehlerschritte schnell, langsam
 ELSE
   ' y ist schnelle Richtung
   pdx = 0  : pdy = sdy ' pd. ist Parallelschritt
   ddx = sdx: ddy = sdy ' dd. ist Diagonalschritt
   es  = adx: el  = ady ' Fehlerschritte schnell, langsam
 END IF
 
 x = xstart
 y = ystart
 SETPIXEL x,y
 fehler = el/2
 
 REM Pixelschleife: immer ein Schritt in schnelle Richtung, hin und wieder auch einer in langsame
 FOR i=1 TO el          ' el gibt auch Anzahl der zu zeichnenden Pixel an
    REM Aktualisierung Fehlerterm
    fehler = fehler - es
    IF fehler < 0 THEN
       fehler = fehler + el ' Fehlerterm wieder positiv (>=0) machen
       REM Schritt in langsame Richtung
       x = x + ddx: y = y + ddy ' Diagonalschritt
    ELSE
       REM Schritt in schnelle Richtung
       x = x + pdx: y = y + pdy ' Parallelschritt
    END IF
    SETPIXEL x,y
 NEXT

C-Implementierung[Bearbeiten]

In dieser Implementierung wird Gebrauch von der Signumfunktion gemacht.

/* signum function */
int sgn(int x){
  return (x > 0) ? 1 : (x < 0) ? -1 : 0;
}
 
void gbham(int xstart,int ystart,int xend,int yend)
/*--------------------------------------------------------------
 * Bresenham-Algorithmus: Linien auf Rastergeräten zeichnen
 *
 * Eingabeparameter:
 *    int xstart, ystart        = Koordinaten des Startpunkts
 *    int xend, yend            = Koordinaten des Endpunkts
 *
 * Ausgabe:
 *    void SetPixel(int x, int y) setze ein Pixel in der Grafik
 *         (wird in dieser oder aehnlicher Form vorausgesetzt)
 *---------------------------------------------------------------
 */
{
    int x, y, t, dx, dy, incx, incy, pdx, pdy, ddx, ddy, es, el, err;
 
/* Entfernung in beiden Dimensionen berechnen */
   dx = xend - xstart;
   dy = yend - ystart;
 
/* Vorzeichen des Inkrements bestimmen */
   incx = sgn(dx);
   incy = sgn(dy);
   if(dx<0) dx = -dx;
   if(dy<0) dy = -dy;
 
/* feststellen, welche Entfernung größer ist */
   if (dx>dy)
   {
      /* x ist schnelle Richtung */
      pdx=incx; pdy=0;    /* pd. ist Parallelschritt */
      ddx=incx; ddy=incy; /* dd. ist Diagonalschritt */
      es =dy;   el =dx;   /* Fehlerschritte schnell, langsam */
   } else
   {
      /* y ist schnelle Richtung */
      pdx=0;    pdy=incy; /* pd. ist Parallelschritt */
      ddx=incx; ddy=incy; /* dd. ist Diagonalschritt */
      es =dx;   el =dy;   /* Fehlerschritte schnell, langsam */
   }
 
/* Initialisierungen vor Schleifenbeginn */
   x = xstart;
   y = ystart;
   err = el/2;
   SetPixel(x,y);
 
/* Pixel berechnen */
   for(t=0; t<el; ++t) /* t zaehlt die Pixel, el ist auch Anzahl */
   {
      /* Aktualisierung Fehlerterm */
      err -= es;
      if(err<0)
      {
          /* Fehlerterm wieder positiv (>=0) machen */
          err += el;
          /* Schritt in langsame Richtung, Diagonalschritt */
          x += ddx;
          y += ddy;
      } else
      {
          /* Schritt in schnelle Richtung, Parallelschritt */
          x += pdx;
          y += pdy;
      }
      SetPixel(x,y);
   }
} /* gbham() */

Kompakte Variante[Bearbeiten]

Der Bresenham-Algorithmus kann auch in einer einfachen Variante in C implementiert werden:

void line(int x0, int y0, int x1, int y1)
{
  int dx =  abs(x1-x0), sx = x0<x1 ? 1 : -1;
  int dy = -abs(y1-y0), sy = y0<y1 ? 1 : -1;
  int err = dx+dy, e2; /* error value e_xy */
 
  for(;;){  /* loop */
    setPixel(x0,y0);
    if (x0==x1 && y0==y1) break;
    e2 = 2*err;
    if (e2 > dy) { err += dy; x0 += sx; } /* e_xy+e_x > 0 */
    if (e2 < dx) { err += dx; y0 += sy; } /* e_xy+e_y < 0 */
  }
}

Das Fehlerglied wird dabei sowohl für die langsame als auch die schnelle Richtung als Schritterkennung verwendet. Die vier Quadranten werden über das Vorzeicheninkrement (sx, sy) abgedeckt. Die Akkumulation des Fehlerglieds löst bei Überschreitung des Schwellwertes den bedingten Schritt aus, im Unterschied zur ursprünglichen Variante simultan in beide Richtungen: positive Fehlerwerte für x und negative für die y-Achse. Das Beispiel zeigt damit auch elegant die xy-Symmetrie des Bresenham-Algorithmus.

Kreisvariante des Algorithmus[Bearbeiten]

Rastern einer Kreislinie nach dem Bresenham-Verfahren

Der Ansatz für die Kreisvariante des Bresenham-Algorithmus geht auch nicht auf Bresenham selbst zurück, er ähnelt der Methode von Horn aus dem Übersichtsartikel, siehe auch Pitteway und van Aken unten. Man geht entsprechend von der Kreisgleichung x^2+y^2=r^2 aus. Man betrachtet zunächst wieder nur den ersten Oktanten. Hier möchte man eine Kurve zeichnen, die beim Punkt (r,0) anfängt und dann nach oben links bis zum Winkel von 45° fortgesetzt wird.

Die „schnelle“ Richtung ist hier die y-Richtung. Man macht immer einen Schritt in die positive y-Richtung, und ab und zu muss man auch einen Schritt in die „langsame“, negative x-Richtung machen.

Die ständigen Quadratberechnungen (siehe Kreisgleichung) oder womöglich sogar trigonometrische oder Wurzelberechnungen vermeidet man wieder durch Auflösen in Einzelschritte und rekursive Berechnung der Terme aus den jeweils vorangehenden.

Mathematisch: Von der Kreisgleichung kommt man zur umgeformten Gleichung

0 = x^2 + y^2 - r^2,

wobei r^2 gar nicht explizit berechnet werden muss,

x^2 = (x_\mathrm{vorher}-1)^2 = x_\mathrm{vorher}^2-2 \cdot x_\mathrm{vorher}+1 (entsprechend für y),

wobei auch hier x_\mathrm{vorher}^2 nicht als eigene Variable mitgeführt werden muss, sondern nur die Differenz von einem Schritt zum nächsten (-2 \cdot x_\mathrm{vorher}+1) dem Fehlerglied aufgeschlagen wird. Wieder wird die Null in der Gleichung durch das Fehlerglied ersetzt.

Zusätzlich muss man dann beim Setzen der Pixel noch die Mittelpunktskoordinaten hinzuaddieren. Diese ständigen Festkommaadditionen schränken die Performance aber nicht merkbar ein, da man sich ja Quadrierungen oder gar Wurzelziehungen in der innersten Schleife erspart.

Durch den Ansatz von der Kreisgleichung aus ist sichergestellt, dass die Koordinaten maximal um 1 Pixel, den Digitalisierungsfehler, von der Idealkurve abweichen. Die Initialisierung des Fehlerglieds soll nun bewirken, dass der erste Schritt in die langsame Richtung dann erfolgt, wenn die echte Kreiskurve um ein halbes Pixel in der langsamen Koordinate nach innen gekommen ist. Details zur Rechnung weiter unten, es läuft auf eine Initialisierung des Fehlerglieds mit dem Radius r hinaus.

Die Formulierung wird hier wieder nur für den ersten Oktanten gezeigt, und wieder ergeben sich die anderen Oktanten durch Vorzeichenwechsel in dx und dy und Rollenvertauschung von x und y. Die Erweiterung auf den Vollkreis, wie sie für Grafikdisplays, aber nicht für Plotter geeignet ist, ist in Kommentaren beigefügt.

 REM Bresenham-Algorithmus für einen Achtelkreis in Pseudo-Basic
 REM gegeben seien r, xmittel, ymittel
 REM Initialisierungen für den ersten Oktanten
 x = r
 y = 0
 fehler = r
 REM "schnelle" Richtung ist hier y!
 SETPIXEL xmittel + x, ymittel + y
 
 REM Pixelschleife: immer ein Schritt in schnelle Richtung, hin und wieder auch einer in langsame
 WHILE y < x
    REM Schritt in schnelle Richtung
    dy = y*2+1 : REM bei Assembler-Implementierung *2 per Shift
    y = y+1
    fehler = fehler-dy
    IF fehler<0 THEN
       REM Schritt in langsame Richtung (hier negative x-Richtung)
       dx = 1-x*2 : REM bei Assembler-Implementierung *2 per Shift
       x = x-1
       fehler = fehler-dx
    END IF
    SETPIXEL  xmittel+x, ymittel+y
    REM Wenn es um einen Bildschirm und nicht mechanisches Plotten geht,
    REM kann man die anderen Oktanten hier gleich mit abdecken:
    REM SETPIXEL xmittel-x, ymittel+y
    REM SETPIXEL xmittel-x, ymittel-y
    REM SETPIXEL xmittel+x, ymittel-y
    REM SETPIXEL xmittel+y, ymittel+x
    REM SETPIXEL xmittel-y, ymittel+x
    REM SETPIXEL xmittel-y, ymittel-x
    REM SETPIXEL xmittel+y, ymittel-x
 WEND

Eine mögliche Implementierung des Bresenham-Algorithmus für einen Vollkreis in C. Hierbei wird für die quadratischen Terme eine weitere Variable mitgeführt, die dem Term 2 \cdot n + 1 von oben entspricht, sie muss von einem Schritt zum nächsten lediglich um 2 erhöht werden:

  void rasterCircle(int x0, int y0, int radius)
  {
    int f = 1 - radius;
    int ddF_x = 0;
    int ddF_y = -2 * radius;
    int x = 0;
    int y = radius;
 
    setPixel(x0, y0 + radius);
    setPixel(x0, y0 - radius);
    setPixel(x0 + radius, y0);
    setPixel(x0 - radius, y0);
 
    while(x < y)
    {
      if(f >= 0)
      {
        y--;
        ddF_y += 2;
        f += ddF_y;
      }
      x++;
      ddF_x += 2;
      f += ddF_x + 1;
 
      setPixel(x0 + x, y0 + y);
      setPixel(x0 - x, y0 + y);
      setPixel(x0 + x, y0 - y);
      setPixel(x0 - x, y0 - y);
      setPixel(x0 + y, y0 + x);
      setPixel(x0 - y, y0 + x);
      setPixel(x0 + y, y0 - x);
      setPixel(x0 - y, y0 - x);
    }
  }

Herleitung der Fehlerglied-Initialisierung[Bearbeiten]

Den Schnittpunkt, an dem die Kreislinie um 1/2 Pixel nach innen gekommen ist, berechnet man nach der Kreisgleichung:

\, x^2 + y^2 = r^2    oder    y = \sqrt{r^2 - x^2}

Am gefragten Punkt (x1,y1) soll gelten: \, x_1 = r - 1/2 , also ergibt sich:

 y_1 = \sqrt{r^2 - (r-1/2)^2} = \sqrt{r^2 - (r^2 - r + 1/4)} = \sqrt{r - 1/4}

Da bis hierhin noch kein x-Schritt stattgefunden haben soll, ist das Fehlerglied mit


\sum_{y=0}^{y_1}(2*y+1) = y_1^2

zu initialisieren, wobei y1² durch Runden zu r wird.

Beispiel: im Bild oben: r=11, y1=3 (abgerundet), Fehlerglied bei y=y1=3 ist 1+3+5=9, Fehlerglied bei y=4 ist 1+3+5+7=16. Wurde das Fehlerglied mit r=11 initialisiert, so findet der erste Vorzeichenwechsel und damit der erste x-Schritt tatsächlich beim Übergang von y1 zu y1+1 statt.

Zeichnen nicht-vollständiger Oktanten[Bearbeiten]

Die obigen Implementierungen zeichnen immer nur komplette Oktanten bzw. Kreise. Wenn man nur einen bestimmten Kreisbogen von einem Winkel \alpha bis zu einem Winkel \beta zeichnen will, muss man das so implementieren, dass man sich die x- und y-Koordinaten dieser Endpunkte im Vorhinein berechnet, wobei man unvermeidlich auf Trigonometrie oder Wurzelrechnung zurückgreifen muss (s. a. Heron-Verfahren). Dann lässt man den Bresenham-Algorithmus über den kompletten Oktanten bzw. Kreis laufen und setzt die Pixel aber nur dann, wenn sie in den gewünschten Bereich fallen. Nach Beendigung dieses Kurvenstücks kann man den Algorithmus vorzeitig abbrechen.

Ellipsen[Bearbeiten]

Auch für Ellipsen gibt es wieder mehrere mögliche Ansätze. Man kann bei achsenparallelen Ellipsen von der entsprechenden Gleichung

\frac{x^2}{a^2}+\frac{y^2}{b^2}=1

wobei a und b die beiden Halbachsenlängen angeben, ausgehen und dann ähnlich wie oben beim Kreis vorgehen.

Man kann aber auch durch Skalierung der gezeichneten x- und y-Werte (und ggf. horizontaler bzw. vertikaler Linienerweiterungen) auf Basis des Kreisalgorithmus solche achsenparallele Ellipsen erzeugen. Dabei benutzt man den Kreisalgorithmus mit der kleineren Ellipsenachse als Radius und addiert in der anderen Richtung einen Wert hinzu, den man wiederum per Bresenham-Linienalgorithmus ansteigend vom Pol zum Äquator ermitteln kann. Da die Ellipse in die längere Achsenrichtung gestreckt werden muss, setzt man dann nicht nur einzelne Pixel, sondern muss ggf. eine Linie (allerdings eine einfache, horizontale oder vertikale) vom vorherigen Punkt zum nächsten ziehen.

Eine allgemeine Ellipse kann man aus so einer achsenparallelen gewinnen, indem man die komplette Grafik zusätzlich einer Scherung unterwirft. Wieder benutzt man einen zusätzlichen Bresenham-Linienalgorithmus, um den Versatz in eine der Achsenrichtungen ansteigend zu ermitteln und ihn bei jeder zu zeichnenden Koordinate einzubeziehen.

Kompakte Variante[Bearbeiten]

Wie bei dem Algorithmus für die Linie kann auch die Kreisvariante xy-symmetrisch formuliert werden. Damit kann also ein kontinuierlicher Viertelkreis gezeichnet werden, was bei Ellipsen hilfreich ist.

void ellipse(int xm, int ym, int a, int b)
{
   int dx = 0, dy = b; /* im I. Quadranten von links oben nach rechts unten */
   long a2 = a*a, b2 = b*b;
   long err = b2-(2*b-1)*a2, e2; /* Fehler im 1. Schritt */
 
   do {
       setPixel(xm+dx, ym+dy); /* I. Quadrant */
       setPixel(xm-dx, ym+dy); /* II. Quadrant */
       setPixel(xm-dx, ym-dy); /* III. Quadrant */
       setPixel(xm+dx, ym-dy); /* IV. Quadrant */
 
       e2 = 2*err;
       if (e2 <  (2*dx+1)*b2) { dx++; err += (2*dx+1)*b2; }
       if (e2 > -(2*dy-1)*a2) { dy--; err -= (2*dy-1)*a2; }
   } while (dy >= 0);
 
   while (dx++ < a) { /* fehlerhafter Abbruch bei flachen Ellipsen (b=1) */
       setPixel(xm+dx, ym); /* -> Spitze der Ellipse vollenden */
       setPixel(xm-dx, ym);
   }
}

Der Algorithmus testet dabei immer einen Diagonalschritt und korrigiert diesen bei zu großer Abweichung. Die Schritte enden aber immer im nächsten Quadranten und dann wird bei flachen Ellipsen (b=1) zu früh abgebrochen. In diesen Fällen ist also eine Ergänzung notwendig. Die Fehlervariable muss übrigens den 3-fachen Wertebereich (Stellenanzahl, Bits) vom Radius (Halbachsen) aufweisen (etwa 64-bit oder Gleitkommazahl).

Die Methode kann auch für Kreise (a=b=r) verwendet werden. Die Vereinfachung (indem etwa die Fehlervariable durch 2r2 gekürzt wird) führt dann zu den oben gezeigten Kreisbeispielen. Aus vier nahtlosen Viertelkreisen wird so ein kontinuierlicher Vollkreis, wie es etwa bei Plottern erforderlich ist.

Weitere Verallgemeinerungen[Bearbeiten]

Bereits im Jahre 1968 wurde die Idee publiziert, den Bresenham-Algorithmus für die Digitalisierung von durch kubische Gleichungen beschriebenen Kurven zu verallgemeinern.[5] Wirklich ausgeführt wurden die Details erst 1993 u. a. von Pitteway[6] und unabhängig davon in US-Patent 5717847.[7] Eine Anwendung zur Strukturierung im Sub-Mikrometer-Bereich von durch rationale kubische Bezierkurven berandeten geometrischen Figuren fand das Verfahren in dem Lithografie-Tool LION-LV1.[8]

Weblinks[Bearbeiten]

 Commons: Bresenham-Algorithmus – Sammlung von Bildern, Videos und Audiodateien

Literatur[Bearbeiten]

  1. J. E. Bresenham: Algorithm for computer control of a digital plotter. IBM Systems Journal 4, 1 (1965), S. 25–30, ISSN 0018-8670 (PDF, 223 KB, engl.) Bereits 1963 als Vortrag auf der ACM National Conference in Denver präsentiert.
    Die erste Veröffentlichung der Grundidee für die Kreisgenerierung findet sich in H. B. Keller, J. R. Swenson: Experiments on the lattice problem of Gauss. Math. Comp. 17 (1963), S. 223–230 (PDF; 788 kB), Section 3.
  2. Pitteway, M. L. V.: Algorithm for Drawing Ellipses or Hyperbolae with a Digital Plotter, Computer J., 10(3) November 1967, S. 282–289 (engl.)
  3. Van Aken, J. R.: An Efficient Ellipse Drawing Algorithm, CG&A, 4(9), September 1984, S. 24–35 (engl.)
  4. F. G. Stockton, XY Move Plotting, Communications of the ACM, vol. 4, no. 6, April 1963, p. 161 (engl.)
  5. R. J. Botting, M. L. V. Pitteway: Cubic extension of a conic section algorithm. Computer Journal 11 (1968), S. 120
  6. Fiaz Hussain, Michael L. V. Pitteway: Rasterizing the outlines of fonts. (PDF; 165 kB) Electronic Publishing Band 6 (1993), Nr. 3, S. 171–181
  7. Method for generating plane technical curves or contours, filed Dec. 23, 1993
  8. R. Plontke: LION-LV1: Ein Lithographie-System für Integrierte Optik und Nanostrukturen. in: Jenaer Jahrbuch zur Technik- und Industriegeschichte, Band 9 (2006), S. 535–566