![]() |
FloodFill Rekursiv
Hi,
Ich benötige für mein Vorhaben eine FloodFill Procedure. Dazu hab ich erst mal eine "normale" programmiert. Aber irgendwie bekomm ich dauernd nen Stack-Overflow... Wodran liegt denn das? Ich hab bei Wikipedia geguckt ob die das anders machen aber Wikipedia machts exakt genauso!
Delphi-Quellcode:
Gruß
procedure FloodFill(ACanvas: TCanvas; x,y: Integer; AColor: TColor; Border: TColor);
begin if (ACanvas.Pixels[x,y] <> Border) then begin ACanvas.Pixels[x,y] := AColor; FloodFill(ACanvas,x+1,y,AColor,Border); FloodFill(ACanvas,x-1,y,AColor,Border); FloodFill(ACanvas,x,y+1,AColor,Border); FloodFill(ACanvas,x,y-1,AColor,Border); end; end; Neutral General |
Re: FloodFill Rekursiv
Nur ne Vermutung, aber du hast nicht wirklich ne vernünftige Endbedingung, ausser wenn ALLES eingefärbt ist.
Will sagen, checke wenigstens mal ob 0<X<maxX und 0<Y<maxY..... |
Re: FloodFill Rekursiv
Ja das hatte ich mir auch schon gedacht aber ich hab die Fläche jetzt so eingegrenzt das sie nicht am Ende des Canvas ist sondern in der Mitte. Also ist die Abbruchbedingung auf jeden Fall erfüllt.. Wenn ich nur eine Richtung Rekursiv ausführe dann gehts.. Bei mehreren gibts nen Stack overflow.
|
Re: FloodFill Rekursiv
Delphi-Quellcode:
procedure FloodFill(ACanvas: TCanvas; x,y: Integer; AColor: TColor; Border: TColor);
begin if (ACanvas.Pixels[x,y] <> Border) then begin ACanvas.Pixels[x,y] := AColor; // Überprüfe vorm Aufruf erst, ob der Pixel die Bordercolor hat! Dann ruft er FloodFill nur auf, wenn nötig! FloodFill(ACanvas,x+1,y,AColor,Border); FloodFill(ACanvas,x-1,y,AColor,Border); FloodFill(ACanvas,x,y+1,AColor,Border); FloodFill(ACanvas,x,y-1,AColor,Border); end; end; |
Re: FloodFill Rekursiv
So ganz macht deinen Code auch kein Sinn --->
bspw. wird Pixel 10,10 bearbeitet --> ist ungleich Border und wird auf Color gesetzt --> rek. Aufruf auf alle umliegenden Pixel --> der umliegende Pixel ruft ja wieder das Feld 10,10 auf.... der Vergleich ergibt true, da Border und AColor bestimmt unterschiedlich sind --> endlosschleife |
Re: FloodFill Rekursiv
Ok thx. Also ich habs jetzt so abgeändert:
Delphi-Quellcode:
Das klappt auch bei manchen flächen.. Aber bei Flächen die viel Bitmap-Rand enthalten gibts wieder en Stack-Overflow... -.-
procedure FloodFill(ABmp: TBitmap; x,y: Integer; AColor: TColor; Border: TColor);
begin if (ABmp.Canvas.Pixels[x,y] <> Border) and (ABmp.Canvas.Pixels[x,y] <> AColor) and (x <= ABmp.Width) and (x >= 0) and (y <= ABmp.Height) and (y >= 0) then begin ABmp.Canvas.Pixels[x,y] := AColor; FloodFill(ABmp,x,y+1,AColor,Border); FloodFill(ABmp,x,y-1,AColor,Border); FloodFill(ABmp,x+1,y,AColor,Border); FloodFill(ABmp,x-1,y,AColor,Border); end; end; Gruß Neutral General |
Re: FloodFill Rekursiv
Zitat:
|
Re: FloodFill Rekursiv
|
Re: FloodFill Rekursiv
Zitat:
Danke :) Gruß Neutral General |
Re: FloodFill Rekursiv
Zitat:
Gammatester |
Re: FloodFill Rekursiv
Hi,
Hätte nochmal ne Frage: Ich fülle jetzt die Fläche mehrfarbig.. Also mit verschiedenen Helligkeiten der angegebenen Farbe. Und da funktioniert ja folgende Abbruchbedingung nicht mehr:
Delphi-Quellcode:
Wie mach ich das denn?
(ABmp.Canvas.Pixels[x,y] <> AColor)
Gruß Neutral General |
Re: FloodFill Rekursiv
Dann musst du dir die Pixel merken, an denen du schon warst!
|
Re: FloodFill Rekursiv
grml das hatte ich mir fast gedacht das es nicht einfacher geht... Geht das ganze FloodFillen eigentlich auch schneller? Hab das Gefühl das das FloodFill von Windows um einiges schneller ist...
Gruß Neutral General |
Re: FloodFill Rekursiv
Wenn der Hintergrund einfarbig ist, und diese Farbe nicht in deinem Füllmuster vorkommt, prüfe auf Gleichheit mit der Referenzfarbe (Startpixel, der ja üblich auf dem Hintergrund liegt).
Wenn du nicht sicherstellen kannst, dass die Farbe nicht in der Füllung vor kommt, bleibt als ultimative Lösung noch, ein 2D Array aus Bools, mit den selben Ausmaßen wie dein Bitmap. Setze dort alles auf false wo der korespondierende Pixel im Bitmap deine Randfarbe ist, den Rest auf true. Dann setzte immer false, wenn du einen Punkt füllst. Dann kannst du in diesem Array nachsehen, ob du einen Pixel bereits bearbeitet hast, oder ob du am Rand bist. \\Edith vermisst den roten Kasten, und ist zudem der Meinung, dass Canvas.Pixels[] der langsamst mögliche Zugriff auf Pixeldaten ist. Canvas.Scanline geht da schon ganz anders ab, und das dürfte den ärgsten Flaschenhals ausmachen bei dir. Eine weitere Optimierung wäre noch möglich, indem du den 4 Folgeaufrufen mitteilst, welchen der 4 weiteren nicht mehr zu betrachten sind, da du ja daher kommst. Wenn man das einfach mit 0..3 durchnummeriert, kannst du mit einer einfachen case-Anweisung ein paar Vergleiche sparen. Aber Pixels ist ganz sicher der weiiiit größere Hammer. |
Re: FloodFill Rekursiv
Ja ich muss es schon mit einem Array machen... Der Hintergrund ist nämlich mehrfarbig. Aber wie siehts mit meiner zweiten Frage aus: Warum ist mein Floodfill langsamer als das von Canvas (auch einfarbig!) ? Kann ich meins irgendwie schneller machen?
|
Re: FloodFill Rekursiv
...muss euch nochmal um Hilfe bitten bei der Sache:
Das ganze sieht jetzt so aus:
Delphi-Quellcode:
Seitdem ich das mehrfarbige Floodfill hab gibts en Stackoverflow. Dann hab ich halt das BooleanArray hinzugefügt aber es kommt immernoch ein Stack Overflow -.-
procedure FloodFill(ABmp: TBitmap; x,y: Integer; AColor: TColor; Border: TColor; var Map: TBoolmap);
var l,i: Integer; r,g,b: Byte; c: TColor; begin if Length(Map) = 0 then begin SetLength(Map,Abmp.Height); for i:= 0 to ABmp.Height-1 do SetLength(Map[i],ABmp.Width); end; if Map[y,x] then exit; if (ABmp.Canvas.Pixels[x,y] <> Border) and (x >= 0) and (x <= ABmp.Width-1) and (y <= ABmp.Height-1) and (y >= 0) then begin l := GetRValue(ABmp.Canvas.Pixels[x,y]); r:= cut(GetRValue(AColor)-(255-l)); g:= cut(GetGValue(AColor)-(255-l)); b:= cut(GetBValue(AColor)-(255-l)); c := RGB(r,g,b); ABmp.Canvas.Pixels[x,y] := c; Map[y,x] := true; FloodFill(ABmp,x,y+1,AColor,Border,Map); FloodFill(ABmp,x,y-1,AColor,Border,Map); FloodFill(ABmp,x+1,y,AColor,Border,Map); FloodFill(ABmp,x-1,y,AColor,Border,Map); end; end; Wodran liegt das denn jetzt bitte ? -.- Gruß Neutral General |
Re: FloodFill Rekursiv
Zuerst ein paar Anmerkungen zum Code, wo Dinge auftauchen, die ich für umständlich bzw. komisch gelöst halte:
1) In diesem Fall würde ich eine Nested-Function verwenden, um u.a. das mehrmalige Übergeben der Map zu umgehen. Das verhindert auch, dass die Map von aussen mitgegeben werden muss, was ja garkeinen Sinn ergibt, da sie rein interne Angelegenheit des Floodfills ist. Strukturell in etwa so:
Delphi-Quellcode:
So in etwa würd ich das vermutlich machen =)
procedure FloodFill(ABmp: TBitmap; x, y: Integer; AColor: TColor; Border: TColor);
var Map: TBoolmap; ix, iy: Integer; procedure DoPixel(ax, ay: Integer); begin if ({ax und ay im gültigen Bereich}) and not Map[ax, ay] then begin ABmp.Canvas.Pixels[ax, ay] := Zielfarbe; Map[ax, ay] := true; DoPixel(ax+1, ay ); DoPixel(ax , ay+1); DoPixel(ax-1, ay ); DoPixel(ax , ay-1); end; end; begin // Bei SetLength gehen so viele Argumente wie dein Array Dimensionen hat ;) // Zudem ist es so "richtig herum", erst x dann y Koordinate. Bei dir ists vertauscht // und damit anders herum als bei Pixels. Unschön und fehlerträchtig. SetLength(Map, ABmp.Width, ABmp.Height); // Map initialisieren, und dabei gleich die Border abtragen. Erspart die zusätzliche // Abfrage auf diese für jedes Pixel nachher. for iy := 0 to ABmp.Heigt-1 do for ix := 0 to ABmp.Width-1 do if ABmp.Canvas.Pixels[ix, iy] = Border then Map[ix, iy] := true else Map[ix, iy] := false; // Eigentichen Fill starten DoPixel(x, y); end; 2)
Delphi-Quellcode:
Naja, ich hab zwar geschrieben, dass es bis Width/Height-1 geht, aber performanter und lesbarer wäre es gewesen statt obigem folgendes zu machen:
(x <= ABmp.Width-1) and (y <= ABmp.Height-1)
Delphi-Quellcode:
Selber Effekt, ohne noch zusätzliche Arithmetik. Und wenn ich nicht irre, ist ein Vergleich auf echt kleiner oder größer sogar noch ne Picosekunde schneller als auf kleiner/größer-gleich :freak:
(x < ABmp.Width) and (y < ABmp.Height)
3) Dein Stack overflow ist im übrigen interessant! Ich hätte eher eine Bereichsverletzung, oder mindestens ne AV erwartet. Bei:
Delphi-Quellcode:
Greifst du auf Elemente zu, die evtl. garnicht mehr im Bereich sind. Ob x und y zwischen 0 und Width/Height sind, prüfst du erst später.
if Map[y,x] then
exit; Gruss, Fabian |
Re: FloodFill Rekursiv
Nochmal zum Overflow:
Wie groß ist dein Bild, bzw. der zu füllende Bereich ca.? Diese Methode zum Füllen tendiert nämlich dazu, seeeehr tief in die Rekursion zu gehen. Die Erste "Füll-Ameise" läuft ja durch, bis sie in einer Sackgasse landet. Das kann im Optimalfall schon der gesamte Füllbereich sein! Je nach Größe, Form und Startpunkt wandern dann mehrere tausend Aufrufe aufm Stack. Wenn das der Fall ist, und andere Quellen für den Overflow auszuschließen sind, gäbe es zwei Auswege:
Es gibt noch einen 3. Ausweg, der aber extrem unschön ist, und nur Symptome bekämpft: In den Projektoptionen den Stack vergrößern. Auf den ersten Blick eine tolle Idee, weil einfach und super schnell gemacht, aber sehr unfeiner Stil und wenn jemand ein noch etwas größeres Bild nimmt, schon wieder im Eimer. Zunächst gilt es aber alle anderen Fehlerquellen als "zu groß" auszuschließen. (Zumal das dann wirklich schon nen ganz schön großer Bereich sein müsste. So Fullscreen-Fill oder sowas :D) Anmerkung: Witziger weise ist es was die Rekursionstiefe angeht besser für den Algo, wenn die zu füllende Form möglichst komplex ist, mit schön zerklüfteten Rändern. Ein ausgefranstes Tribal-Tatoo z.B. wäre prima :mrgreen:. Während ein einfaches Viereck z.B. schlimmstenfalls in einer einzigen Spirale gefüllt würde, was einer Rekursionstiefe von Breite*Höhe des Vierecks in Pixeln entspräche. (Je nach Wahl der Reihenfolge der zu untersuchenden Nachbarpixel, und dem Startpixel. Es kann auch etwas günstiger ausfallen.) Alles in allem ist Floodfill recht interessant, und weit weniger trivial, als man zunächst annehmen möchte finde ich =). Man kann viel optimieren, verallgemeinern, rumdoktorn - herrlich! |
Alle Zeitangaben in WEZ +1. Es ist jetzt 14:09 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz