Forum: Mikrocontroller und Digitale Elektronik Punkte eines Kreises berechnen


von Johannes (Gast)


Lesenswert?

Moin

Ich möchte die Punkte von einem Kreis berechnen. Hierzu verwende ich 
folgende Formel:

r^2 = x^2 + y^2

nach x umgestellt:

x = (r^2 - y^2)^1/2

Demnach muss ich die Wurzel ziehen. Kann dies ein µC überhaupt?

x =sqrt(r^2 - y^2);

Ist das ein Problem für den µC?

Ich benutze einen SAM7 von Atmel

Grüsse

Johannes

von holger (Gast)


Lesenswert?

>Demnach muss ich die Wurzel ziehen. Kann dies ein µC überhaupt?

Ja, wieso nicht ;)
Such mal nach Bresenham da gehts ohne Wurzel.

von Hagen R. (hagen)


Lesenswert?

In der CodeLib findest du einige Grafikroutinen für diverse LCDs, die 
enthalten ASM und C Source um Kreise, Ellipsen und abgerundete Rechtecke 
zu zeichnen. Fast alle benutzen Bresenham.

Gruß Hagen

von Jean Meyer (Gast)


Lesenswert?

Mit dem iterativen Heron-Verfahren kriegst du die Wurzel so genau 
angenähert wie du willst: http://de.wikipedia.org/wiki/Heron-Verfahren

von Matthias L. (Gast)


Lesenswert?

Wenns darum geht, den Kreis bzw Teile/Punkte davon zu zeichnen, wieso 
gehst du nicht über Sinus/Kosinus?


x =  R_x * SIN ( phi )
y =  R_y * COS ( phi )

R_x, R_y: Radius in x, y Richtung (Gleich für Kreis)
phi     : WInkel

von Hagen R. (hagen)


Lesenswert?

weil das langsammer ist und nicht Pixelgenau hinzubekommen ist. Entweder 
bei zu geringer Rechengenauigkeit ist die Kreislinie nich geschlossen 
oder bei zu hoher Genauigkeit berechnet man die selben Pixel mehrfach.

Wenn es nur darum geht solche Kreis und Ellipsen auf einem Rasterdevice 
zu zeichnen dann ist Bresenham das Beste. Mit diesen Alogs. berechnet 
man nur 1/4'tel des Kreise und zeichnet die anderen Teile durch 
Spiegelungen. Alle Berechnungen sind nur abhängig von der Auflösung des 
Displays und Integer Operationen, in der innersten Schleifen nur 
Additionen und Subtraktionen.
In jedem Schritt des Algos. wird durchschnittlich 1 Pixel erechnet, aber 
das hängt davon ab was man benötigt.

Gruß Hagen

von Sebastian (Gast)


Lesenswert?

Gibt es dazu auch einen guten Quellcode in C?

von Hagen R. (hagen)


Lesenswert?

CodeLib oder schick mir ne PN und ich mail dir meine S65 Library.

Gruß Hagen

von Aleksandar (Gast)


Lesenswert?

Hallo!

Kreis Zeichnen geht VIEEEEEEL schneller. Sieh Dir folgendes an, ist 
relativ gut beschrieben:

http://de.wikipedia.org/wiki/Bresenham-Algorithmus

Das grundlegende Prinzip besteht darin, dass der naechste Punkt bei 
einer Aufteilung des Kreises in 8 Oktanten nur um einen Pixel oder 
keinen Punkt nach unten (oder halt oben) gehen kann. Es reicht aus, 
auszurechnen, ob beim naechten Schritt die Abweichung groesser ist, wenn 
man den Punkt um einen Pixel verschiebt oder nicht.

x^2 + y^2 = r^2

Bei einem Schritt von x auf x+1 andert sich die Summe links um 2*x + 1. 
Du braucht jetzt nur festzustellen ob (x+1)^2 + (y+1)^2 naeher als 
(unveraendertes y) (x+1)^2 + y^2 an r^2 kommt.

Selbstlob muss auch sein: ;)
Damit habe ich auf ZX-Spectrum gefuellte Kreise mit Schraffur im 
Assembler etwa 20 mal schneller gezeichnet als Original-BASIC nur Kreise 
malte. Die haben woll Sinus/Cosinus benutzt :(

Viel Erfolg!
Aleks

von Christoph db1uq K. (christoph_kessler)


Lesenswert?

Hier gibts die Original-Veröffentlichung von Bresenham 1965:
http://researchweb.watson.ibm.com/journal/sj/041/ibmsjIVRIC.pdf

Bei Answers.com gibts noch mehr Algorithmen:
http://www.answers.com/Bresenham%20algorithm
oder Goertzel, der Tondecoderalgorithmus
http://www.answers.com/topic/goertzel-algorithm

von Christoph db1uq K. (christoph_kessler)


Lesenswert?

oh answers.com ist ja nur eine Suchmaschine, die betten einfach die 
englische Wikipedia ein, ich dachte das wäre was eigenes, dabei 
schmücken die sich nur mit fremden Federn.

von Johannes (Gast)


Lesenswert?

Ich habe es so implementiert wie im Beispiel bei Wikipedia.

http://de.wikipedia.org/wiki/Bresenham-Algorithmus

Es funktioniert auch gut. Jedoch ist die Strichstärke mit einem Pixel zu 
dünn, deshalb wollte ich 2 Kreise zeichen. Den 1. mit einem Radius von 
20 und den 2. mit einem Radius von 21. Jedoch entstehen dabei minimale 
lücken zwischen den beiden Kreisen. Kann ich dies verhindern?

Grüsse

Johannes

von l00k (Gast)


Lesenswert?

Einfach an die berechnete Position statt eines einzelnen Pixels 4 Pixel 
(2x2) setzen. Sollte dann einen sauberen Kreis mit 2 Pixeln Stärke 
ergeben.

von Hagen R. (hagen)


Lesenswert?

Ja, indem du deinen Bresenham ganz leicht abänderst. Du kannst also die 
Linienstärke direkt mit dem Algo. beeinflussen.

Ich habe das so gemacht das ich kariertes Papier genommen, meinen Kreis 
nach Bresenham eingezeichnet habe, und dann erkenntst du schon wie du 
das ändern musst.

Zb. statt einen Pixel auch ein Pixel groß zu machen, kannst du einen 
Pixel als Block aus 4 Pixel zusammenbauen. Das bringt aber nicht so gute 
Ergebnisse.

Gruß hagen

von Hagen R. (hagen)


Lesenswert?

zb. einen 1.5 Pixel breiten Kreis baust du indem du in deinem Besenham 
bei einem Schritt in dem X und Y Koordinate sich verändern, zusätzlich 
Pixel zeichnest.

Gruß Hagen

von Johannes (Gast)


Lesenswert?

Das habe ich jetzt nicht ganz verstanden. Soll ich einen Kreis mit 20.5 
Pixel Radius zeichen oder wie ist das gemeint.


 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);
   }
 }


Grüsse

Johannes

von Hagen R. (hagen)


Lesenswert?

if f >= 0

machst du 2. Schritte, einmal in Y und danach in X Richtung. Af deinem 
karierten Papier würden sich der alte und neue Pixel nur an einer Ecke 
berühren. Das ist der richtige Zeitpunkt um zb. SetPixel(x0 - x -1, y0 - 
y -1) aufzurufen. Die entstehende Linie würde alle Pixel an deren Kanten 
verbunden zeichnen. Das erscheint dann dem Auge als 1.5 Pixel Linie.

Gruß hagen

von Hagen R. (hagen)


Lesenswert?

nachfolgend ein Ausschnitt meine S65 GLCD Library.
Im Gegensatz zu deiner Kreisfunktion bauen meine Kreis,Ellipsen und 
RoundRect Funktionen auf der gleichen basisfunktion auf.

Man kann also einen Kreis zeichnen per Mittelpunk + Radius, oder 
innerhalb eines Quadrates. Wäre es ein beliebiges Rechteck so entsteht 
eine Ellipse.

Der Source zeichnet aber auch nur ein 1. Pixel breites Objekt. Da aber 
die Koordinaten des Objektes als Rechteck/Quadrat angegeben werden kann 
man hier mit 2 mal nacheinander das Quadrat als Kreis gezeichnet 
unterschiedliche Stärken zeichnen.

Und noch eines, der Source unterstützt das Ausfüllen der Objekte. 
Deshalb ist er ein bischen komplexer.

Gruß Hagen
1
void glcdSetPixel4(glcdCoord_t x1, glcdCoord_t y1, glcdCoord_t x2, glcdCoord_t y2, glcdColor_t color) {
2
3
    glcdSetPixel(x1, y1, color);
4
    glcdSetPixel(x2, y1, color);
5
    glcdSetPixel(x1, y2, color);
6
    glcdSetPixel(x2, y2, color);
7
}
8
9
void glcdLine2(glcdCoord_t x1, glcdCoord_t y1, glcdCoord_t x2, glcdCoord_t y2, glcdColor_t color) {
10
11
    glcdFillRect(x1, y1, x2, y1, color);
12
    glcdFillRect(x1, y2, x2, y2, color);
13
}
14
15
void glcdRoundRect(glcdCoord_t x1, glcdCoord_t y1, glcdCoord_t x2, glcdCoord_t y2, glcdCoord_t a, glcdCoord_t b, glcdColor_t fgcolor, glcdColor_t bkcolor) {
16
17
    if (x1 > x2) glcdSwapCoord(x1, x2);
18
    if (y1 > y2) glcdSwapCoord(y1, y2);
19
    glcdCoord_t t;
20
    t = x2 - x1;
21
    t >>= 1;
22
    if (a > t) a = t;
23
    t = y2 - y1;
24
    t >>= 1;
25
    if (b > t) b = t;
26
    x1 += a;
27
    x2 -= a;
28
    if (fgcolor != NONE) glcdLine2(x1, y1, x2, y2, fgcolor);
29
    uint16_t as = a * a, bs = b * b;
30
    int32_t dx = 0, dy = as, ee = bs;
31
    dy *= b;
32
    ee += as / 4;
33
    ee -= dy;
34
    dy += dy;
35
    while (dx < dy) {
36
      if (fgcolor != NONE) glcdSetPixel4(x1, y1, x2, y2, fgcolor);
37
      x1--;
38
      x2++;
39
      if (ee >= 0) {
40
        y1++;
41
        y2--;
42
        dy -= as;
43
        dy -= as;
44
        ee -= dy;
45
        if (bkcolor != NONE) glcdLine2(x1, y1, x2, y2, bkcolor);
46
      }
47
      dx += bs;
48
      dx += bs;
49
      ee += bs;
50
      ee += dx;
51
  }
52
  int32_t tt = as;
53
    tt -= bs;
54
  tt *= 3;
55
  tt >>= 1;
56
  tt -= dx;
57
  tt -= dy;
58
  tt >>= 1;
59
    ee += tt;
60
    while (y1 <= y2) {
61
      if (bkcolor != NONE) glcdLine2(x1, y1, x2, y2, bkcolor);
62
      if (fgcolor != NONE) glcdSetPixel4(x1, y1, x2, y2, fgcolor);
63
      y1++;
64
      y2--;
65
      if (ee < 0) {
66
        x1--;
67
        x2++;
68
        a++;
69
        dx += bs;
70
        dx += bs;
71
        ee += dx;
72
      }
73
      dy -= as;
74
      dy -= as;
75
      ee += as;
76
      ee -= dy;
77
    }
78
}
79
80
void glcdEllipse(glcdCoord_t x1, glcdCoord_t y1, glcdCoord_t x2, glcdCoord_t y2, glcdColor_t fgcolor, glcdColor_t bkcolor) {
81
82
    if (x1 > x2) glcdSwapCoord(x1, x2);
83
    if (y1 > y2) glcdSwapCoord(y1, y2);
84
    glcdRoundRect(x1, y1, x2, y2, (x2 - x1) / 2, (y2 - y1) / 2, fgcolor, bkcolor);
85
}
86
87
void glcdCircle(glcdCoord_t x, glcdCoord_t y, glcdCoord_t r, glcdColor_t fgcolor, glcdColor_t bkcolor) {
88
89
    glcdRoundRect(x - r, y - r, x + r, y + r, r, r, fgcolor, bkcolor);
90
}

von Johannes (Gast)


Lesenswert?

Es mag ja sein das diese Funktion super funktioniert, jedoch sieht sie 
sehr undurchsichtig aus, da kein Komentar in Deinem Quellcode vorhanden 
ist.

Genau das gleich Problem habe ich beim Wikipediaquellcode.

ich versuche immer diesen Teil zu verstehen:

while(x < y)
   {
     if(f >= 0)
     {
       y--;
       ddF_y += 2;
       f += ddF_y;
     }
     x++;
     ddF_x += 2;
     f += ddF_x + 1;
   }

x ist zu Beginn ja 0 und y = radius. Dies ist noch nachfolziehbar. 
Jedoch hört es hier schon auf

   int f = 1 - radius;
   int ddF_x = 0;
   int ddF_y = -2 * radius;

Dies verstehe ich z.B. schon nicht mehr.

Was mich noch mehr wundert ist die While-Scheife, diese läuft solange x 
< Y. Ich dachte man rechnet nur 45° aus und den rechts durch 
Punktsynmetrie

Grüsse

Johannes

von Hagen R. (hagen)


Lesenswert?

Naja, die Frage ist was will man daran kommentieren ?
Das glcdEllipse() eine Ellipse zeichnet ?

Der Rest ist eben ein abgewandelter Bresenham und dessen Funktionsweise 
sollte man kennen wenn man den Source verändern möchte.

1.) du zeichnest ja deine 8'tel Segmente, alle deine SetPixel() 
Funktionen machen das.

2.) man berechnet einen fortlaufenden Fehlerwert der angibt wie weit man 
mit den zu zeichnenden Pixelkoordinaten von der Ideallinie entfernt ist. 
Überschreitet dieser Wert eine Differenz von 1 Pixel müssen die 
Koordinaten angepasst werden.

3.) man berechnet ausgehend von einer linearen X Koordinaten Erhöhung um 
+1 den Fehler in der Y Koordinaten

4.) deshalb reicht der Vergleich der X Y Koordinaten als while Bedingung 
aus

5.) versteht man den Source so versteht man Bresenham

6.) der einzigst fehlende Kommentar wäre demnach "das ist Bresenham der 
Variante X", der Rest sucht der Programmierer im WEB, Wikipedia, Bücher 
oä.

7.) die Aufgabe des Sources besteht also darin diese Objekte zu zeichnen 
und nicht daraus ein lehrbuch zu machen


Gruß Hagen

von Hagen R. (hagen)


Lesenswert?

Achso ganz vergesssen:

Dein Source geht in 8'tel Segmenten vor meiner in 4'tel Segmenten. Damit 
ist dein Code zum Zeichnen von Kreisen doppelt so effizient, aber nicht 
zwangsläufig doppelt so schnell. Der Unterschied besteht darin das dein 
Code nur Kreise zeichnen kann, also alle 8 Segmente haben die gleiche 
Krümmung. Bei meinem Code gibt es 2 unterschieldiche Krümmungen, was 
auch notwendig ist um Ellipsen zeichnen zu können.

Gruß Hagen

von Karl H. (kbuchegg)


Lesenswert?

Johannes wrote:
> Es mag ja sein das diese Funktion super funktioniert, jedoch sieht sie
> sehr undurchsichtig aus, da kein Komentar in Deinem Quellcode vorhanden
> ist.

Kommentare helfen beim Bresenham nicht viel.
Entweder man hat verstanden wie der Bresenham grundsätzlich
funktioniert, oder man muss durch die Herleitung des Verfahrens
durch. Aus dem Quellcode ist das so nicht ersichtlich, warum
der Bresenham überhaupt funktioniert. Wenn man ein paar
Beispiele am Rasterpapier durchspielt, gehts so halbwegs.

Aber am einfachsten ist, man sieht sich die Herleitung an.
zb. hier
http://www.hdm-stuttgart.de/~rk020/Files/Computeranimation/rastergraphics/printable_version.html

Das muesste man dann auch alles als Block-Kommentar einfügen
und das ist nun mal ein bischen viel (und auch nicht sinnvoll).

von Hagen R. (hagen)


Lesenswert?

vielleicht noch eine Erklärung warum mein Source so anderst aussieht als 
die herkömmlichen Bresenham Implementierung.

Meistens arbeitet man Mittelpunktzentriert. In deiner Kreisfunktion zb. 
arbeitest du ausgehend von X0,Y0 und bei jedem Schritt errechnest du die 
Koordinaten der SetPixel() Funktonen neu.

In meinem Code arbeite ich die Koordinaten des umschließenden Rechteckes 
ab. Das bedeutet das ich in den Zwischenschritten nicht nur die Radien 
berechne sondern alle 4 Koordinaten updaten muß. Das ist doppelt mehr an 
Aufwand. Da wir aber dann die SetPixel(), Line() Funktionen aufrufen 
wollen und diese mit absoluten Koordinaten arbeiten, ersparen wir uns so 
das ständig Umrechnen in absolute Koordinaten. Das beschleunigt dann 
einiges diese Routine.

Das hat aber noch ganz andere Vorteile. Wenn wir zb. ein Clipping 
einbauen wollen, also eine globale Variable eines Rechteckes das einen 
Bildschirmausschnitt darstellt in das gezeichnet werden darf. Die 
Beschneidung unserer Zeichenroutinen kann man mit meinem Source viel 
einfacher durchführen, da wir ja immer die absoluten statt relativen 
Koordinaten berechnen.

Ich weiß leider selber nicht wie meine Variante heist, bzw. ob es 
überhaupt dafür einen Namen gibt. Das dürfte wohl eher nicht der Fall 
sein da es im Grunde ganz trviale Optimierungen sind. Da ich schon zu 
Uralt DOS Zeiten solche Grafikfunktionen implementiert habe weiß ich 
auch nicht mehr woher ich das habe.

Das einzisgste was ich mir quasi ausgedacht habe ist der Punkt das man 
bei der Angabe von kleinen Radien für A und B auch Rechtecke mit 
abgerundeten Ecken zeichnen kann. Zb. auch eine Art Rechteck das 
aussieht wie ein Faß.

Gruß Hagen

von Johannes (Gast)


Lesenswert?

Erst ma vielen Dank für Eure Hilfe.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.