Ringe zeichnen - geht das besser?

Hallo!

Ich nutze momentan sehr viel mein I²C-OLED und habe vorhin festgestellt, dass meine Lib keine Möglichkeit bietet, einen ausgefüllten Ring auf den Schirm zu bringen. Da ich gerne auch die Mitte des Rings durchsichtig haben wollte, kam es auch nicht in Frage, einen großen Kreis mit einem kleinen Kreis in der anderen Farbe darin zu zeichnen. Also habe ich ein wenig mich mit dem Kreis-Bresenham-Algorithmus auseinander gesetzt, der u.a. bei der “Adafruit GFX”-Lib zum Einsatz kommt, und habe folgendes daraus gemacht (für einen Quadranten, mir ist klar, dass er nicht fertig ist):

#include <Wire.h>
#include "SH1106Wire.h" 

SH1106Wire display(0x3c, SDA, SCL);

void ring(int16_t x0, int16_t y0, int16_t radius, int16_t radius2){
  int16_t x = 0, y = radius;
  int16_t dp = 1 - radius;
  
  int16_t x2 = 0, y2 = radius2;
  int16_t dp2 = 1 - radius2;
  do {
    if (dp < 0)
      dp = dp + (x++) * 2 + 3;
    else
      dp = dp + (x++) * 2 - (y--) * 2 + 5;

    if(x2 < y2){
      if (dp2 < 0)
        dp2 = dp2 + (x2++) * 2 + 3;
      else
        dp2 = dp2 + (x2++) * 2 - (y2--) * 2 + 5;
    }
    else{
      x2++;
      y2++;
    }
    
    display.setPixel(x0 - x, y0 - y);
    display.setPixel(x0 - x2, y0 - y2);
    display.drawLine(x0 - x, y0 - y, x0 - x, y0 - y2);
    //display.drawHorizontalLine(x0 - x, y0 + y, 2*x + 1);
    display.setPixel(x0 - y, y0 - x);
    display.setPixel(x0 - y2, y0 - x2);
    display.drawLine(x0 - y, y0 - x, x0 - y2, y0 - x);
    //display.drawHorizontalLine(x0 - y, y0 + x, 2*y + 1);
    display.display();
    delay(250);

  } while (x < y);
  display.setPixel(x0, y0);
  display.display();
}
void setup() {
  display.init();
  display.flipScreenVertically();
  display.clear();
  
  display.setColor(WHITE);
  ring(32, 32, 20, 10);
  
  display.setColor(INVERSE);
  ring(96, 32, 20, 10);
}
void loop() {
  
}

Der Algorithmus funktioniert mit WHITE perfekt. Aber wenn ich ihn mit INVERSE benutze, dann bekomme ich einen schwarzen Strich in der Mitte (s. Anhang). Ich weiß, dass das eigentlich egal ist, wenn ich einen INVERSE-Ring haben will, dann mache ich das halt mit zwei gewöhnlichen Kreisen, aber das finde ich irgendwie nicht ganz zufriedenstellend, zumal das ja bedeutet, dass ich Pixel doppelt färbe, was ja Verschwendung von CPU-Leistung ist. Gibt es eine Möglichkeit, dieses Problem leicht und sinnreich zu beheben? Oder habe ich vielleicht eine ganz falsche Herangehensweise gewählt?

Gruß
HTML-Fan

Und wieso wieder 2 Threads zum selben Thema ?

Tut mir leid, liegt an meinem schlechten WLAN.

HTML-Fan:
Tut mir leid, liegt an meinem schlechten WLAN.

Ahhh....ok, hätte ich jetzt auch gesagt.

Oh nein! Er hat meine Aussage über den neuesten Weltuntergang geschickt als Ausrede enttarnt! :o

HTML-Fan:
… Gibt es eine Möglichkeit, dieses Problem leicht und sinnreich zu beheben? Oder habe ich vielleicht eine ganz falsche Herangehensweise gewählt?

Das Zeichnen selbst zu erledigen ist schon einmal ein guter Start. IMO keinesfalls die falsche Herangehensweise.

Mein Vorschlag: Sieh Dir mal den Flood-Fill-Algorithmus an. Was das mit der Transparenz angeht: Zeichne in eine temporäre Zeichenfläche (Array) und übertrage diese dann in das eigentliche Bild. Dabei „überträgst“ Du nur die Pixel, die im temporären Ding gesetzt sind.

Gruß

Gregor

Hey, witzigerweise habe ich mir erst vor zwei, drei Tagen Flood-Fill angeguckt. Aber wenn ich dann die temporäre Geschichte auf den eigentlichen Buffer kriegen will, dann dauert das. Die Ringe sollen über den Bildschirm wandern, das heißt, ich kann da nicht einfach

for(int i = 0;i < x;i++){
  buffer[i] |= buffer2[i];
}

machen, da bräuchte ich Bitverschiebung. Außerdem ist Floodfill ein aufwändiger Prozess. Mir geht es gerade ja darum, diese doppelten Pixel zu eliminieren.

HTML-Fan:
... Außerdem ist Floodfill ein aufwändiger Prozess. Mir geht es gerade ja darum, diese doppelten Pixel zu eliminieren.

Floodfill ist vielleicht ungünstig, wenn es um den Stack geht, aber wenn Du Pixel doppelt füllst, stimmt was an Deiner Floodfill-Variante nicht.

Hm. Vielleicht muss ich meine kleine Universal-Grafikbibliothek doch mal aufmotzen/-polieren und ins Netz stellen ...

Gruß

Gregor

Nein, so meinte ich das nicht. Ich meinte, dass ich bei meinem eigenen Algorithmus diesen doppelt beschriebenen Bereich wegkriegen will. Bei Floodfill färbe ich keine Pixel doppelt, aber ich prüfe alle Pixel einzeln und Pixel einzeln zu beschreiben ist wesentlich aufwändiger als Linien zu zeichnen. Mir geht es in erster Linie um Geschwindigkeit.

HTML-Fan:
Nein, so meinte ich das nicht.

Ach so.

So auf die Schnelle: Es wird Dir wohl nichts Anderes übrig bleiben, statt nur eines Quadranten eine Hälfte des Ringes zu betrachten. Für die Bereiche, die ober-/unterhalb des inneren Kreises liegen und den Bereich dazwischen musst Du dann halt „spezielle“ Varianten des Bresenham-Algorithmus' programmieren.

Gruß

Gregor

Moin!
Wenn Du sicher bist, dass Dein zu füllender Bereich einfach zusammenhängend ist, kannst Du eine "Minimalvariante" des Füllens mit Linien implementieren:

Variante 1 (prüft immer noch alle Pixel):

  • Von links kommend prüfen, ob Farbe von Hintergrund (HG) auf Vordergrund (VG) wechselt -> ignorieren (falls der Bresenham zwei Pixel horizontal nebeneinander gesetzt hat).
  • Weiter prüfen auf Umschlag VG nach HG --> merken "A"
  • Weiter nach rechts prüfen bis HG nach VG --> merken "B"
  • Linie A - B in VG Farbe zeichnen
  • Wiederholen 1 bis 4 bis zum rechten Bildschirmrand.

Variante 2:
Beim Zeichnen der Kreise jedesmal die (aktuelle) x-Koordinate merken, bevor y sich ändert.
Damit solltest Du für jedes y die Paare "A" - "B" zum Linien zeichnen zusammenbekommen.
Spezialfall: Ungerade Anzahl Werte - das ist dann eine "Ein Pixel Spitze" und keine Linie nötig.

Gruß Walter

(der sowas vor 25 Jahren mal in Assembler gießen durfte... das war schön).