Forum: Mikrocontroller und Digitale Elektronik Programmierung: Bresenham f. Plotter/ohne Spiegelung


von Matthias S. (da_user)


Lesenswert?

Hi,

Erstmal: ich war mir nicht sicher, ob ich hier posten soll, oder in 
"PC-Programmierung". Da ich aber auf einen µC programmieren will, habe 
ich mich jetzt mal für hier entschieden.

Prinzpiell denke ich, habe ich den Bresenham für den Kreis verstanden. 
Die meisten Beispiele berechnen meistens nur einen Oktanten, dessen 
Startpunkt wird dann sogar manuell gesetzt. Und dann wird der Oktant 
einfach gespiegelt.

Ich brauche jetzt aber eine Implementierung die wirklich den kompletten 
Kreis berechnet, einmal außenrum.
Ganz frei nach Kathrin Passig: "Mache es nicht selbst, du machst nur die 
gleichen Fehler wie die anderen", traue ich mich jetzt auch nicht 
wirklich das selbst zu implementieren. Insbesondere da ich davon 
ausgehe, dass ich da nur die trölfhundertste Variante davon mache, und 
wahrscheinlich sogar noch eine schlechte.

Leider bringen mir all meine Suchversuche (Bresenham Circle without 
mirroring; Bresenham Kreis ohne Spiegelung; ... plotter, usw. usw.) bei 
Google immer wieder nur Implementierungen mit Spiegelung, ist halt auch 
die einfachste Variante.

Bevorzugte Sprache wäre C/C++, aber solange es eine einigermaßen 
verständliche Hochsprache oder Pseudocode ist, werde ich dass dann schon 
umsetzen können. Mit Assembler, Brainfuck & Co kann ich halt leider nix 
anfangen.

Hintergrund:
Ich will auf ein kleines OLED-Display ein Gauge, also eine analoge 
Anzeige, darstellen. Dazu möchte ich nur einen Teilkreis zeichnen, und 
brauche auch dessen Pixelanzahl um den Anzeigenwert passend darauf zu 
skalieren. Und dann muss ich natürlich auch noch den Punkt berechnen, 
auf dem die "Nadel" gerade sitzt.

Wäre schön, wenn mir hier jemand hier mal in seinem Codearchiv 
nachgucken könnte oder so, und mir kurz unter die Arme greifen kann.

VG
da_user

von Possetitjel (Gast)


Lesenswert?

Matthias S. schrieb:

> Ich brauche jetzt aber eine Implementierung die wirklich
> den kompletten Kreis berechnet, einmal außenrum.

Dann brich nicht ab, sondern lass die Kiste einfach
acht Mal so weit rechnen.

von Sven S. (boldie)


Lesenswert?

So wie ich das verstehe, willst du einen Kreisbogen zeichnen. So etwas 
habe ich schon gemacht, das geht so, dass man sich Anfangs und Endpunkt 
berechnet (also klassisch mit sin / cos o.ä.) und diese dann verwendet, 
um zu entscheiden, ob der Quadrant an sich und im Quadrant dann der 
Pixel gezeichnet werden muss. Das sind ein paar if-Abfragen die man hier 
meistern muss.

von Matthias S. (da_user)


Lesenswert?

Kreisbogen habe ich schon herausen.
Da Ende und Anfang für so eine Skala ja auf einer Linie liegen, geht das 
sogar relativ einfach: ich lass einfach nur Punkte Zeichnen die unter 
(0,0 liegt rechts oben) einem bestimmten Y-Wert liegen. Eine virtuelle 
Grenze auf der Y-Achse quasi.

Für weiterer Anwendungen, also z.B. den Punkt auf den der Zeiger steht, 
oder auch die Beschriftung, brauche ich bestimmte Punkte auf dem 
Teilkreis. Und dazu bräuchte ich die Punkte in der richtigen 
Reihenfolge, darum eben sauber außenrum.

Possetitjel schrieb:
> Dann brich nicht ab, sondern lass die Kiste einfach
> acht Mal so weit rechnen.

Also in den Algorithmen die ich bis jetzt gesehen habe, ist keine 
Abbruchbedingung drinnen. Meine Idee wäre jetzt höchstens, die 
While-Schleife in 8 Varianten (für jeden Oktanten eine) zu schreiben. 
Aber das muss ja schöner gehen ;-)

von Johann Klammer (Gast)


Lesenswert?

Google:
Da Silva "Raster Algorithms for 2D Primitives"

evtl auch HAKMEM item 177, fuer ein low-level incremental path 
encoding(wegen uC).

von Walter T. (nicolas)


Lesenswert?

Matthias S. schrieb:
> Ich will auf ein kleines OLED-Display ein Gauge, also eine analoge
> Anzeige, darstellen.

Das ist länglich.

Bei diesem Projekt ist ein Bresenham für beliebige Kreisabschnitte 
integriert:

http://dl1dow.de/artikel/ftmctrl/index.htm -> glcd_gfxfun.c

Läuft letztendlich auch wieder auf den Standard-Bresenham mit 
angepassten Abbruchbedingungen hinaus.

: Bearbeitet durch User
von Matthias S. (da_user)


Lesenswert?

Hi,

Johann Klammer schrieb:
> Google:
> Da Silva "Raster Algorithms for 2D Primitives"
>
> evtl auch HAKMEM item 177, fuer ein low-level incremental path
> encoding(wegen uC).

ich habe gerade keine Zeit das zu Googlen, trotzdem vielen Dank. Ich 
werde das natürlich möglichst bald nachgooglen.

Auch ist wohl noch nicht so ganz klar, was ich eigentlich vorhabe, ich 
hoffe ich kann das heute Abend etwas genauer ausführen.

Prinzipiell ist das was ich letzen Endes in Sachen Bresenham brauche, 
eine Klasse die ich mit dem Mittelpunkt, Radius und Start-/Endpunkt, 
bzw. meiner Y-Grenze initalisiere und zwei Methoden bereitstellt:
NextPoint() die mir den nächsten gültigen Punkt auf dem Kreis berechnet 
(idealerweise im Uhrzeigersinn)
Reset() die die Klasse auf den Anfangswert zurücksetzt.

Das jetzt mal in der Kürze, wie geschrieben, hoffe ich, dass ich das 
heute Abend noch etwas weiter ausführen kann.

von Possetitjel (Gast)


Lesenswert?

Matthias S. schrieb:

> Auch ist wohl noch nicht so ganz klar, was ich
> eigentlich vorhabe, ich hoffe ich kann das heute
> Abend etwas genauer ausführen.

Dochdoch, ich glaube schon.


> Prinzipiell ist das was ich letzen Endes in Sachen
> Bresenham brauche, eine Klasse die ich mit dem
> Mittelpunkt, Radius und Start-/Endpunkt, bzw. meiner
> Y-Grenze initalisiere und zwei Methoden bereitstellt:
> NextPoint() die mir den nächsten gültigen Punkt auf
> dem Kreis berechnet (idealerweise im Uhrzeigersinn)
> Reset() die die Klasse auf den Anfangswert zurücksetzt.

Ja, das ist verstanden worden.

Das Problem ist: Du suchst einen incrementellen Algo-
rithmus, der zu gegebenem Radiusvektor (Mittelpunkt -->
Kreispunkt) einen Radiusvektor auf den benachbarten
Kreispunkt liefert. Das ist mit den Mitteln der Vektor-
rechnung ziemlich elegant machbar.

Bresenham basiert aber darauf, dass sich die zu zeich-
nende Figur als FUNKTION auffassen lässt -- sogar noch
schärfer: Bresenham basiert darauf, dass sich die Figur
als Funktion auffassen lässt, deren Anstieg den Betrag 1.0
höchstens erreicht, aber garantiert nie überschreitet.
  Dann kann man die "schnelle" Koordinate in Einer-Schritten
ändern, und die "langsame" Koordinate bleibt entweder kon-
stant oder macht auch einen Einer-Schritt.
Bresenham kann somit einen Vollkreis nie durchgängig in einem
Rutsch zeichnen, weil sich der Vollkreis nicht als Funktion
im X-Y-Koordinatensystem darstellen lässt.

Soll heißen: Den Algorithmus, den Du suchst, gibt es --
aber es ist nicht zwingend ein "Bresenham-Algorithmus".

von Matthias S. (da_user)


Lesenswert?

Oh, ok.

Was wäre denn dann ein besser Algorithmus?

von Possetitjel (Gast)


Lesenswert?

Matthias S. schrieb:

> Was wäre denn dann ein besser Algorithmus?

Fertiggerichte kenne ich keine. Ein paar Anregungen bietet
https://de.wikipedia.org/wiki/Rasterung_von_Kreisen

Meine Lieblingsvariante (Rekursionsformel für eine
ungedämpfte Schwingung) habe ich allerdings auch nicht
gefunden.

von Thomas F. (igel)


Lesenswert?

Matthias S. schrieb:
> Ich will auf ein kleines OLED-Display ein Gauge, also eine analoge
> Anzeige, darstellen.

Wie klein ist es denn? Das bekannte 128x160 Pixel Dings aus China?

Bei kleinen Anzeigen würde ich die einzelnen Segmente mit Start- und 
Endpunkt einfach vorher berechnen und in einer Tabelle ablegen. Dann nur 
noch die Linien zeichnen.

von Jim Beam (Gast)


Lesenswert?

Warum machst Du es nicht einfach mit der Standard-Kreisformel über 
sin(winkel)/cos(winkel), wenn Du schon C am nutzt.

Damit sind Start/Enwinkel/Radius ja einfach beherrschbar.
Problem wäre nur die Winkel-Schrittweite mit der Du die Funktion 
iterativ aufrufst:

Ist die Schrittweite zu gross, dann entstehen Pixel-Lücken.
Ist die zu klein, dann erhälst Du ggf. mehrfach den gleiche x-y-Punkt.

Lösung wäre: Jeweils den letzten x-y-Punkt merken und bei jedem Aufruf 
der "nächstes-Pixel-Funktion" solange mit kleiner Winkel-Schrittweite 
erhöhen bis x oder y sich ändern. Diese Werte dann zurückgeben, und 
alles wieder von vorne.

von Matthias S. (da_user)


Angehängte Dateien:

Lesenswert?

Jim Beam schrieb:
> Warum machst Du es nicht einfach mit der Standard-Kreisformel über
> sin(winkel)/cos(winkel), wenn Du schon C am nutzt.

Weil das ganze auf einen Mikrocontroller läuft, und da sin/cos einfach 
zu teuer sind. Insbesondere, da ich die wohl auch mehrmals durcharbeiten 
muss.

Thomas F. schrieb:
> Wie klein ist es denn? Das bekannte 128x160 Pixel Dings aus China?

SSD1351 18x128x128 SPI

Thomas F. schrieb:
> Bei kleinen Anzeigen würde ich die einzelnen Segmente mit Start- und
> Endpunkt einfach vorher berechnen und in einer Tabelle ablegen. Dann nur
> noch die Linien zeichnen.

Das Problem ist es ja, die Linien zu zeichnen ;-)

Possetitjel schrieb:
> Fertiggerichte kenne ich keine. Ein paar Anregungen bietet
> https://de.wikipedia.org/wiki/Rasterung_von_Kreisen
>
> Meine Lieblingsvariante (Rekursionsformel für eine
> ungedämpfte Schwingung) habe ich allerdings auch nicht
> gefunden.

Auch wieder entweder mit teuren cos/sin, Wurzeln und Potenzen, oder 
Bresenham bzw. Varianten davon. Aber mal sehen, ob sich auf Basis dessen 
was basteln lässt.

Die Grundidee ist folgende:
Die komplette Anzeige soll Kreisförmig sein. Der Kreis hat eine Breite 
von, jetzt angenommen 6 Pixel. Also brauche ich 6 Kreise, von innen nach 
außen K1 bis K6. Der Innere Kreis K1 ist der Teilkreis der Skala. Im 
Bild wäre zu sehen wie der aussehen könnte.
Denn habe ich jetzt mit dem Bresenham aus Wikipedia gemacht. Ich glaube 
da brauche ich keinen Code dazu zu posten, ich habe halt dann statt 
"setPixel" eine eigene Methode aufgerufen, die so aussieht:
1
  int GaugePixelCounter = 0;
2
  void CheckAndDrawPixelsOverBorder(byte x, byte y, byte borderY)
3
  {
4
    if (y < borderY)
5
    {
6
      Oled->drawPixel(x, y);
7
      GaugePixelCounter++;
8
    }
9
  }
Mittelpunkt ist hierbei 64,64, Durchmesser 40 und BorderY = 80.
Der hat jetzt, wenn ich mich recht erinnere 149Pixel.

Ich würde dann als nächstes den äußersten Kreis K6 ausrechnen, aber 
nicht zeichnen. Angenommen der hat dann 160Pixel.
Angezeigt werden soll dann von 0 bis 5 bar. Macht 5 Segmente. Auf K1 
wären das knapp 30Pixel/Segment, auf K6 genau 32. Wenn ich jetzt die 
jeweiligen Pixelpositonen für Pixel Nr. 0/0 (K1/K6 - nicht x,y!); 30/32; 
60/64; 90/96; 120/128 und 149/160 ausrechne, habe ich die jeweiligen 
Start und Endpunkte für die Skalenunterteilungslinien.

Die Teilkreise K3, K4 und K5 sollen jetzt den Anzeigebalken darstellen. 
Also genauso vorgehen: Erstmal ausrechnen, wieviele Pixel das sind, dann 
den Wertbereich darauf skalieren und bis zum entsprechenden Pixel 
zeichnen.

Soweit die Idee ;-)

Walter T. schrieb:
> Bei diesem Projekt ist ein Bresenham für beliebige Kreisabschnitte
> integriert:
>
> http://dl1dow.de/artikel/ftmctrl/index.htm -> glcd_gfxfun.c
>
> Läuft letztendlich auch wieder auf den Standard-Bresenham mit
> angepassten Abbruchbedingungen hinaus.

Das sind leider keine beliebigen Kreisabschnitte, sondern Oktanten. Da 
wird dann einfach je nach gewünschten Oktanten die Spiegelung ein oder 
ausgeschaltet. Trotzdem Danke!

Johann Klammer schrieb:
> Google:
> Da Silva "Raster Algorithms for 2D Primitives"
>
> evtl auch HAKMEM item 177, fuer ein low-level incremental path
> encoding(wegen uC).

Beides ist auf jeden Fall mal was, was man sich in ruhiger Minute zu 
Gemüte führen muss ;-)
Bei Da Silva habe ich jetzt beim ersten Überfliegen allerdings 
festgestellt, dass der wohl auch Oktantenweise zeichnet.

Wahrscheinlich wird es darauf hinauslaufen müssen, dass ich den 
Bresenham 8 mal für jeden Oktanten implementieren muss. Unschön, aber 
nicht unmöglich.

von Walter T. (nicolas)


Lesenswert?

Matthias S. schrieb:
> Das sind leider keine beliebigen Kreisabschnitte, sondern Oktanten.

Das sind beliebige Kreisabschnitte. Diese werden aus Oktanten + Rest 
zusammengesetzt.

In diesem Fall für nur einen bestimmten Durchmesser (26 Pixel), weil der 
Sinus nur als Lookuptable hinterlegt ist - aber das läßt sich ja einfach 
anpassen.

: Bearbeitet durch User
von Matthias S. (da_user)


Lesenswert?

Ach... hirnklatsch
Ich hätte noch ein bisschen weiter runterscrollen sollen.
Bin mir aber noch nicht sicher, ob mir das weiterhilft.

von Leo C. (rapid)


Angehängte Dateien:

Lesenswert?

Vielleicht kannst Du ja mit meinen Kreissektor-Funktionen etwas 
anfangen.
Algorithmus stammt von [1].
Die Funktionen sind für die Grafik-Lib U8g2 [2] von Oliver Kraus 
geschrieben und noch nicht ganz fertig (Das zugehörige Projekt ruht 
leider seit über einem halben Jahr).


[1] 
https://stackoverflow.com/questions/13652518/efficiently-find-points-inside-a-circle-sector
[2] https://github.com/olikraus/u8g2/

von Possetitjel (Gast)


Lesenswert?

Matthias S. schrieb:

> Possetitjel schrieb:
>> Fertiggerichte kenne ich keine. Ein paar Anregungen bietet
>> https://de.wikipedia.org/wiki/Rasterung_von_Kreisen
>>
>> Meine Lieblingsvariante (Rekursionsformel für eine
>> ungedämpfte Schwingung) habe ich allerdings auch nicht
>> gefunden.
>
> Auch wieder entweder mit teuren cos/sin, Wurzeln und Potenzen,

Quatsch.

Die Rekursion ist im Prinzip nur
x_n+1 := x_n+f*yn
y_n+1 := y_n-f*x_n+1

Das Problem besteht in den diversen Kleinigkeiten.

von Walter T. (nicolas)


Lesenswert?

Leo C. schrieb:
> Vielleicht kannst Du ja mit meinen Kreissektor-Funktionen etwas
> anfangen.

Womit hast Du die Screenshots/Darstellung auf dem PC gemacht?

von Matthias S. (da_user)


Lesenswert?

Possetitjel schrieb:
> Quatsch.
> Die Rekursion ist im Prinzip nur x_n+1 := x_n+f*yn y_n+1 := y_n-f*x_n+1
> Das Problem besteht in den diversen Kleinigkeiten.

Sorry, war auf den Wikipedialink bezogen.

: Bearbeitet durch User
von Leo C. (rapid)


Lesenswert?

Walter T. schrieb:
> Leo C. schrieb:
>> Vielleicht kannst Du ja mit meinen Kreissektor-Funktionen etwas
>> anfangen.
>
> Womit hast Du die Screenshots/Darstellung auf dem PC gemacht?

Mit sdl.
In der u8g2-lib gibts ein ein Unterverzeichnis sys/sdl mit Beispielen.

von Thomas F. (igel)


Lesenswert?

Matthias S. schrieb:
> Also brauche ich 6 Kreise, von innen nach
> außen K1 bis K6....

Bei 120x120 Pixel einfarbig (1 Bit/Pixel) bräuchte man 1,8kByte Speicher 
für ein bereits fertiges Bild des Zeigerinstruments. Ich würde da nicht 
lange rumrechen wollen.

Dann müsste man nur noch den Zeiger zeichnen.

von Matthias S. (da_user)


Lesenswert?

Leo C. schrieb:
> Vielleicht kannst Du ja mit meinen Kreissektor-Funktionen etwas
> anfangen.

Ähm... Wow.
Auf den ersten Blick sieht das tatsächlich nach was passenden aus. Aber 
da raucht jetzt erstmal der Kopf.
Aber ja, ich denke das dürfte wir doch deutlich weiterhelfen, da muss 
ich mich allerdings noch "etwas" mit dem Code und den Artikel 
beschäftigen.

Ich nutze übrigens die UCG Arduino von Oli Kraus.

Vielen Dank!

: Bearbeitet durch User
von Leo C. (rapid)


Lesenswert?

Hier ist noch die Funktion, die das Poti auf dem Foto oben gemalt hat.
1
#ifndef DISPLAY_WIDTH
2
#define DISPLAY_WIDTH   256
3
#endif
4
#define DISPLAY_HIGHT   64
5
6
#define POT_R           37            //Poti radius in pixel
7
#define POT_WIDTH       5
8
#define POT_X0          (DISPLAY_WIDTH-POT_R)  //Poti center
9
#define POT_Y0          37
10
11
#define POT_MSG_FONT    u8g2_font_crox5t_tr
12
13
14
void draw_pot(u8g2_t *p, uint_fast8_t val, char *text)
15
{
16
    unsigned v = (val * 270) / 255;
17
18
    u8g2_DrawDisc_sector(p, POT_X0, POT_Y0, POT_R, 135, 135 + v);
19
    u8g2_DrawCircle(p, POT_X0, POT_Y0, POT_R-(POT_WIDTH/2), U8G2_DRAW_ALL);
20
    uint_fast8_t dc = p->draw_color;
21
    u8g2_SetDrawColor(p, dc ? 0 : 1);        // complement draw color
22
    u8g2_DrawDisc(p, POT_X0, POT_Y0, POT_R-POT_WIDTH, U8G2_DRAW_ALL);
23
    u8g2_SetDrawColor(p, dc);        // set draw color to old value
24
25
    u8g2_SetFont(p, POT_MSG_FONT);
26
    u8g2_SetFontPosCenter(p);
27
    int xpos = u8g2_GetStrWidth(p, text)/2;
28
    u8g2_DrawStr(p, POT_X0 - xpos, POT_Y0, text);
29
}

von Matthias S. (da_user)


Angehängte Dateien:

Lesenswert?

Hi,

ich bin jetzt zu einer Lösung gekommen. Die beiden dazugehörigen Dateien 
im Anhang.

Prinzipiell ist das ganze relativ simpel:
Es wird ein neuer Kreis initalisiert, der rechnet erstmal die mit dem 
Bresenham die Punkte eines Oktanten aus und zählt die nur. Dann kann 
wird mit malloc entsprechend RAM reserviert (darum bitte entsprechend 
vorsichtig, möglichst nur einmal initalisieren). Dann wird das Array 
nochmal mit dem Bresenham mit entsprechenden Pixeldaten gefüllt.

Jetzt kann man mit "GetNextPixel" einen Pixel nach dem anderen im 
Uhrzeigersinn unten beginnend durchgehen. Ob weitere Pixel verfügbar 
sind, kann man mit "NextPixelavaible" prüfen. Zudem werden ungültige 
Pixel mit den Werten von -1 zurückgeben.
Mit der Resetmethode kann der Kreis dann zurückgesetzt und von vorne 
begonnen werden.

Mit GetPixel kann dann ein bestimmter Pixel bestimmt werden.

Dessen switch-case Zeug hatte ich anfangs in ähnlicher Form in der 
GetNextPixel Methode. Da habe ich halt dann einen Pixelindex und einen 
Oktantenindex mitzählen lassen.

Zudem gibt es die GraduatedBresenhamCircle-Klasse.
Hier wird zudem eine "BorderY" übergeben. Damit beginnt der Kreis erst 
mit dem Pixel, dessen Y-Koordinate UNTER diesem Wert liegt (0-Punkt ist 
bei mir links oben). Den angehängten Kreis habe ich damit gemacht.

Getestet habe ich dass ganze auf einem Nucleo F303K8. Da sah man dann 
mit der ersten Variante (switch-case in GetNextPixel) schon, wie sich 
der Kreis aufgebaut hat. Ging zwar flott (gefühlt <0,5s), aber man sah 
es.
Jetzt sieht man von dem aufbau nix mehr.

Ein kleines Problem kann man jetzt noch erkennen:
Ich zeichne in dem Bild eine dicke Kreislinie in dem ich sieben Kreise 
nebeneinander lege. Dabei gibt es ein paar Pixel die schwarz bleiben.
Stört mich für den Moment nicht, ich werde mir dann aber schon noch was 
besseres überlegen müssen, wie ich das schöner mache. Evtl. kann ich 
mich da vom Code von Leo C. (rapid) inspirieren lassen.

Eine weitere Erweiterung wäre denkbar für Ellipsen. Sollte auch realtiv 
einfach klappen.

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.