Entwickler-Ecke
Algorithmen, Optimierung und Assembler - Schneller Flood-Fill Algorithmus
Spaceguide - Mi 22.06.05 14:17
Titel: Schneller Flood-Fill Algorithmus
Hat jemand einen schnellen FF, der korrekt arbeitet? Ich habe schon den schnellen von
http://www.student.kuleuven.ac.be/~m0216922/CG/floodfill.html implementiert, aber ich kriege ihn öfters bei bestimmten Mustern zum Versagen. Von einem Implementierungsfehler meinerseits gehe ich nicht aus, habe alles schon 100x durchgecheckt.
BenBE - Mi 22.06.05 17:37
Ich liebe meine Glaskugel ... nur leider ist die Nutzungszeit pro Woche auf einen Moment begrenzt.
Könntest Du also bitte etwas von deinem Source zur Fehlersuche bereitstellen?
Heiko - Mi 22.06.05 17:44
@Spaceguide: Ich habe einen FireFox der schnell und ohne Fehler läuft. Du meinst sicherlich eine funktioniernde FF-Komponente die du brauchst, oder sehe ich das falsch?
delfiphan - Mi 22.06.05 18:05
Heiko: FF=FloodFill (nicht FireFox)
Heiko - Mi 22.06.05 18:16
Aso, sry :oops: . FF ist bei mir immer FireFox ;).
Spaceguide - Mi 22.06.05 20:22
Flood-Fill steht doch in der Überschrift, da gehe ich dann davon aus, dass man im Kontext FF auch als Flood-Fill sieht.
BenBE - Mi 22.06.05 20:31
@Spaceguide: Benötigst Du noch Hilfe oder möchtest Du hier weiter OT diskutieren?
Für ersteres wäre eine Umsetzung meines Postings sinnvoll.
Spaceguide - Mi 22.06.05 20:37
@BenBE: Wie gesagt, ich bin sehr sicher, dass meine Übersetzung nach Delphi fehlerfrei ist und dass der Fehler im Algorithmus selbst liegt. Der Algo liegt in C in der oben genannten Seite vor.
Hier meine Implementierung:
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42:
| push(x, y);
while pop(x, y) do begin y1 := y; while CanVisit(x,y1) and (y1>=0) do Dec(y1); Inc(y1);
spanLeft := false; spanRight := false;
while CanVisit(x,y1) and (y1<Height) do begin AA.SetValueFast(x,y1,255); visited[x+y1*width]:=1;
if (not spanLeft) and (x>0) and CanVisit(x-1,y1) then begin Push(x - 1, y1); spanLeft := true; end else if(spanLeft) and (x>0) and (not CanVisit(x-1,y1)) then begin spanLeft := false; end;
if (not spanRight) and (x<Width-1) and CanVisit(x+1,y1) then begin Push(x + 1, y1); spanRight := true; end else if (spanRight) and (x<Width-1) and (not CanVisit(x+1,y1)) then begin spanRight := false; end;
Inc(y1); end;
end; |
delfiphan - Mi 22.06.05 20:52
Ich hab vor langer Zeit mal ein eigenes FloodFill geschrieben, jedoch auf GetPixel- und Line- Basis. Falls ich es irgendwo finde werde ich es nachher auf Delphi portieren und hier posten. Jedoch wird das dann über Canvas.Pixel laufen. Die Optimierung überlass ich dann dir... Falls ihr in der Zwischenzeit nicht schon eine Lösung findet...
Übrigens: Ich weiss nicht, ob du das gelesen hast:
| Dein Link oben: |
| Do not copy/translate any of the content of this tutorial to a site/book/whatever without my permission. |
//Edit:
Hier kommt der nicht-optimierte Port. Das Optimieren überlass ich dir.
Die Unterprozedur FloodLine greift (via isValid) nur auf die Scanline y, y-1 und y+1 zu. Dort kannst du das Pixels[] durch etwas Schnelleres ersetzen. TStack ist aus der Unit Contnrs. Ist wohl nicht das schnellste. Statt TStack könntest du auch ein statisches Array nehmen. Der Stack hat ohnehin nie mehr als 100 Einträge. Ausser bei sehr komplizierten Bildern geht's vielleicht mal auf 200...
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122:
| procedure FloodFill(const Canvas: TCanvas; const x0, y0, minx, miny, maxx, maxy: Integer; const Color: TColor); var Stack: TStack; initialColor: TColor;
function isValid(const x,y: Integer): Boolean; begin Result := (x>=minx) and (y>=miny) and (x<=maxx) and (y<=maxy) and (Canvas.Pixels[x,y] = initialColor); end;
procedure SetFlag(const x, y: Integer); begin Stack.Push(Pointer(x+y shl 16)); end;
procedure FloodLine(const x, y: Integer); var x1, x2: Integer; G1, G2, F1, F2: Boolean; begin if not isValid(x,y) then exit; if isValid(x,y-1) then begin F1 := True; SetFlag(x,y-1); end else F1 := False; if isValid(x,y+1) then begin F2 := True; SetFlag(x,y+1); end else F2 := False; x1 := x-1; G1 := F1; G2 := F2; while isValid(x1,y) do begin if isValid(x1,y-1) then begin if not G1 then begin SetFlag(x1,y-1); G1 := True; end; end else G1 := False; if isValid(x1,y+1) then begin if not G2 then begin SetFlag(x1,y+1); G2 := True; end; end else G2 := False; dec(x1); end; x2 := x+1; G1 := F1; G2 := F2; while isValid(x2,y) do begin if isValid(x2,y-1) then begin if not G1 then begin SetFlag(x2,y-1); G1 := True; end; end else G1 := False; if isValid(x2,y+1) then begin if not G2 then begin SetFlag(x2,y+1); G2 := True; end; end else G2 := False; inc(x2); end; with Canvas do begin MoveTo(x1+1,y); LineTo(x2,y); end; end;
var x, y: Integer; Temp: DWord; begin Assert(minx>=0); Assert(miny>=0); Assert(maxx<1 shl 16); Assert(maxy<1 shl 16);
initialColor := Canvas.Pixels[x0,y0]; if initialColor = Color then exit; Canvas.Pen.Color := Color; Stack := TStack.Create; try FloodLine(x0,y0); while Stack.Count > 0 do begin Temp := DWord(Stack.Pop); x := Temp and (1 shl 16 - 1); y := Temp shr 16; FloodLine(x,y); end; finally Stack.Free; end; end; |
Spaceguide - Do 23.06.05 02:21
Funktioniert, thanks.
delfiphan - Do 23.06.05 11:35
Hab meinen FloodFill jetzt doch noch ein wenig optimiert (Scanlines, etc.). Bei grösseren Flächen ist mein Algorithmus bis zu 15 mal schneller als Canvas.FloodFill.
//Edit: Kleiner Fehler in der Source korrigiert
Spaceguide - Do 23.06.05 11:51
15 mal? Das ist schon beachtlich. Wie läuft Canvas.Floodfill eigentlich ab? Wird der von Windows bearbeitet oder an den Grafikkartentreiber geschickt und dann eventuell von der GPU übernommen?
delfiphan - Do 23.06.05 11:58
15 ist der grösste von mir beobachtete Wert (wird bei mir erreicht, wenn ich in der Demo (siehe oben) am linken Rand etwa in der Mitte klicke).
Bei kleinen Flächen ist mein Algorithmus "nur" ca. 3-5 mal schneller.
Canvas.FloodFill geht über die gdi32.dll. Was die genau macht, weiss ich nicht. Aber über die GPU geht die Library wohl kaum ;)
Spaceguide - Do 23.06.05 12:24
Grafikkarten haben ja seit zig Jahren schon 2D-Beschleunigerfunktionen, womit das Zeichnen von Fenstern, Menüs und Knöpfen beschleunigt wird. Ich frage mich nur, ob auch alle Befehle auf die Canvas davon profitieren. Kann man ja mal ausprobieren, indem man bei Problembehebung die Beschleunigung abschaltet und dann die Geschwindigkeit misst.
Christian S. - Fr 24.06.05 21:34
Hallo!
Eventuell hilft
dieser [
http://www-gs.informatik.tu-cottbus.de/~wwwgs/cg4_vorles.htm] Link auch noch weiter, der mir vorhin zugetragen wurde. :les: Im Kapitel 5a werden auch Floodfill-Algorithmen teils mit Implementierung besprochen. Hier besonders interessant dürfte die auf Seite 17 besprochene Saatfüll-Methode sein, die davor besprochenen Methoden sind dazu da, wenn das umschließende Polygon bekannt ist.
Sollte das schon der hier besprochene Algorithmus sein, möge man mir das nachsehen. So sehr habe ich mich dann in den Algorithmus doch nicht reingedacht. :gruebel:
Grüße
Christian
delfiphan - Sa 25.06.05 12:21
Coole Zusammenfassungen! Mein Algorithmus ist zwar eine Eigenkonstruktion, funktioniert aber ziemlich ähnlich. Der Unterschied sehe ich darin, dass mein Algorithmus versucht, pro Zeile eine möglichst lange horizontale Linie reinzulegen, in der Zusammenfassung ist's eher punktbasiert.
Ein Vorteil meines Algorithmus ist, dass der Stack (weil linienbasiert) nie sehr gross werden kann, und deswegen auch nicht viel Rekursionsaufrufe nötig sind. (Ein Rekursionsschritt ist dann aber natürlich aufwändiger, jedoch ist die Zahl der Rekursionsaufrufe tiefer (und somit der Stack kleiner))
Bei meinem linienbasierten Verfahren muss man pro Pixel nur ca. 3 andere Pixel lesen (neues Pixel, Pixel drüber und Pixel drunter). Bei Canvas.FloodFill bzw. auch in der Zusammenfassung müssen 4 bzw. 5 Pixel gelesen werden. Deswegen ist die Komplexität dieser Algorithmen grundlegend höher.
IMHO ist das der Grund, wieso mein Algorithmus bei kleinen Flächen ca. 3-4 mal, und bei grossen Flächen sogar bis zu 15 mal schneller ist (kein linearer Zusammenhang).
Entwickler-Ecke.de based on phpBB
Copyright 2002 - 2011 by Tino Teuber, Copyright 2011 - 2026 by Christian Stelzmann Alle Rechte vorbehalten.
Alle Beiträge stammen von dritten Personen und dürfen geltendes Recht nicht verletzen.
Entwickler-Ecke und die zugehörigen Webseiten distanzieren sich ausdrücklich von Fremdinhalten jeglicher Art!