Riempimento allagamento - Flood fill
Flood fill , chiamato anche seed fill , è un algoritmo che determina e altera l'area connessa a un dato nodo in un array multidimensionale con qualche attributo corrispondente. Viene utilizzato nello strumento di riempimento "secchio" dei programmi di disegno per riempire aree collegate di colore simile con un colore diverso e in giochi come Go e Campo minato per determinare quali pezzi vengono eliminati. Una variante denominata riempimento limite utilizza gli stessi algoritmi ma è definita come l'area connessa a un dato nodo che non ha un particolare attributo.
Nota che il riempimento allagamento non è adatto per disegnare poligoni pieni, poiché mancherà alcuni pixel negli angoli più acuti. Invece, vedi Regola pari-dispari e Regola diversa da zero .
I parametri dell'algoritmo
Il tradizionale algoritmo di riempimento richiede tre parametri: un nodo iniziale, un colore di destinazione e un colore sostitutivo. L'algoritmo cerca tutti i nodi nell'array che sono collegati al nodo iniziale da un percorso del colore di destinazione e li cambia nel colore sostitutivo. Per un riempimento del bordo, al posto del colore di destinazione, verrebbe fornito un colore del bordo.
Per generalizzare l'algoritmo nel modo comune, le seguenti descrizioni avranno invece a disposizione due routine. Uno chiamato Inside
che restituisce vero per i punti non riempiti che, per il loro colore, sarebbero all'interno dell'area piena, e uno chiamato Set
che riempie un pixel/nodo. Qualsiasi nodo che lo ha Set
chiamato non deve più essere Inside
.
A seconda che si considerino i nodi che si toccano agli angoli collegati o meno, abbiamo due varianti: rispettivamente a otto ea quattro vie.
Implementazione ricorsiva basata su stack (a quattro vie)
La prima implementazione di riempimento a quattro vie , implicitamente basata su stack, ricorsiva , è la seguente:
Flood-fill (node): 1. If node is not Inside return. 2. Set the node 3. Perform Flood-fill one step to the south of node. 4. Perform Flood-fill one step to the north of node 5. Perform Flood-fill one step to the west of node 6. Perform Flood-fill one step to the east of node 7. Return.
Sebbene di facile comprensione, l'implementazione dell'algoritmo utilizzato sopra è poco pratica in linguaggi e ambienti in cui lo spazio dello stack è fortemente limitato (ad es. Microcontrollori ).
Spostare la ricorsione in una struttura dati
Lo spostamento della ricorsione in una struttura dati (uno stack o una coda ) impedisce un overflow dello stack. È simile alla semplice soluzione ricorsiva, tranne per il fatto che invece di effettuare chiamate ricorsive, spinge i nodi su uno stack o una coda per il consumo, con la scelta della struttura dei dati che influenza il modello di proliferazione:
Flood-fill (node): 1. Set Q to the empty queue or stack. 2. Add node to the end of Q. 3. While Q is not empty: 4. Set n equal to the first element of Q. 5. Remove first element from Q. 6. If n is Inside: Set the n Add the node to the west of n to the end of Q. Add the node to the east of n to the end of Q. Add the node to the north of n to the end of Q. Add the node to the south of n to the end of Q. 7. Continue looping until Q is exhausted. 8. Return.
Ulteriori potenziali ottimizzazioni
- Controlla e imposta il colore dei pixel di ciascun nodo prima di aggiungerlo allo stack/coda, riducendo le dimensioni dello stack/coda.
- Usa un loop per le direzioni est/ovest, accodando i pixel sopra/sotto mentre procedi. (Rendendolo simile agli algoritmi di riempimento dello span, di seguito.)
- Interlaccia due o più copie del codice con stack/code extra, per consentire ai processori OoO maggiori opportunità di parallelizzare
- Usa più thread (idealmente con ordini di visita leggermente diversi, in modo che non rimangano nella stessa area)
Vantaggi
- Algoritmo molto semplice - facile da rendere privo di bug.
Svantaggi
- Utilizza molta memoria, in particolare quando si utilizza uno stack.
- Verifica i pixel più riempiti per un totale di quattro volte.
- Non adatto per il riempimento del motivo, poiché richiede la modifica dei risultati del test dei pixel.
- Il modello di accesso non è compatibile con la cache, per la variante di accodamento.
- Non è possibile ottimizzare facilmente per parole multi-pixel o bitplane.
Riempimento della campata
È possibile ottimizzare ulteriormente le cose lavorando principalmente con gli span. Il primo esempio completo pubblicato funziona sul seguente principio di base. Partendo da un punto seme, riempi a sinistra e a destra e tieni traccia dei bordi. Quindi, scansiona la stessa porzione della linea sopra e la linea sotto, alla ricerca di nuovi seed-point con cui continuare. Questo algoritmo è il più popolare, sia per le citazioni che per le implementazioni, nonostante abbia testato la maggior parte dei pixel pieni tre volte in totale. In forma pseudo-codice:
fn fill(x, y): if not Inside(x, y) then return let s = new empty stack or queue add (x, y) to s while s is not empty: Remove an (x, y) from s let lx = x while Inside(lx - 1, y): Set(lx - 1, y) lx = lx - 1 while Inside(x, y): Set(x, y) x = x + 1 scan(lx, x - 1, y + 1, s) scan(lx, x - 1, y - 1, s) fn scan(lx, rx, y, s): let added = false for x in lx .. rx: if not Inside(x, y): added = false else if not added: Add (x, y) to s added = true
Nel tempo sono state realizzate le seguenti ottimizzazioni:
- Quando una nuova scansione sarebbe interamente all'interno di un intervallo nonno, sicuramente troverebbe solo pixel pieni e quindi non avrebbe bisogno di accodamento.
- Inoltre, quando una nuova scansione si sovrappone a una campata del nonno, devono essere scansionate solo le sporgenze (inversioni a U e inversioni a W).
- È possibile riempire durante la scansione dei semi
Il riempitivo finale dell'intervallo di scansione e riempimento combinato è stato quindi pubblicato nel 1990 e procede come segue (sebbene la versione qui corregga alcuni bug nell'originale):
fn fill(x, y): if not Inside(x, y) then return let s = new empty queue or stack Add (x, x, y, 1) to s Add (x, x, y - 1, -1) to s while s is not empty: Remove an (x1, x2, y, dy) from s let x = x1 if Inside(x, y): while Inside(x - 1, y): Set(x - 1, y) x = x - 1 if x < x1: Add (x, x1-1, y-dy, -dy) to s while x1 < x2: while Inside(x1, y): Set(x1, y) x1 = x1 + 1 Add (x, x1 - 1, y+dy, dy) to s if x1 - 1 > x2: Add (x2 + 1, x1 - 1, y-dy, -dy) while x1 < x2 and not Inside(x1, y): x1 = x1 + 1 x = x1
Vantaggi
- 2x-8x più veloce dell'algoritmo ricorsivo in pixel.
- Il modello di accesso è compatibile con la cache e il bitplane.
- Può disegnare una linea orizzontale invece di impostare singoli pixel.
Svantaggi
- Visita ancora i pixel che ha già riempito. (Per il popolare algoritmo, 3 scansioni della maggior parte dei pixel. Per l'ultimo, eseguire solo scansioni extra di pixel dove ci sono buchi nell'area piena.)
- Non adatto per il riempimento del motivo, poiché richiede la modifica dei risultati del test dei pixel.
Aggiunta del supporto per il riempimento del motivo
Due modi comuni per fare in modo che gli algoritmi basati su span e pixel supportino il riempimento del motivo sono utilizzare un colore univoco come riempimento semplice e quindi sostituirlo con un motivo o tenere traccia (in un array booleano 2D o come regioni) di quali pixel sono stati visitati, utilizzandolo per indicare che i pixel non sono più compilabili. Inside deve quindi restituire false per tali pixel visitati.
Riempimento grafico-teorico
Alcuni teorici hanno applicato la teoria dei grafi esplicita al problema, trattando intervalli di pixel, o aggregati di questi, come nodi e studiando la loro connettività. Il primo algoritmo pubblicato sulla teoria dei grafi funzionava in modo simile al riempimento degli span, sopra, ma aveva un modo per rilevare quando avrebbe duplicato il riempimento degli span. Sfortunatamente, aveva dei bug che gli impedivano di completare alcuni riempimenti. Un algoritmo corretto è stato successivamente pubblicato con una base simile nella teoria dei grafi; tuttavia, altera l'immagine mentre procede, per bloccare temporaneamente potenziali loop, complicando l'interfaccia programmatica. Un algoritmo pubblicato in seguito dipendeva dal fatto che il confine fosse distinto da tutto il resto nell'immagine e quindi non è adatto alla maggior parte degli usi; richiede anche un bit extra per pixel per la contabilità.
Vantaggi
- Adatto per il riempimento del motivo, direttamente, poiché non esegue mai nuovamente il test dei pixel riempiti.
- Raddoppia la velocità dell'algoritmo di span originale, per riempimenti semplici.
- Il modello di accesso è compatibile con la cache e il bitplane.
Svantaggi
- Regolarmente, uno span deve essere confrontato con ogni altro "fronte" nella coda, il che rallenta notevolmente i riempimenti complicati.
- Passare avanti e indietro tra la teoria dei grafi e i domini dei pixel complica la comprensione.
- Il codice è abbastanza complicato, aumentando le possibilità di bug.
Riempimento a camminata (metodo a memoria fissa)
Esiste un metodo che essenzialmente non utilizza alcuna memoria per le quattro regioni collegate fingendo di essere un pittore che cerca di dipingere la regione senza dipingersi in un angolo. Questo è anche un metodo per risolvere i labirinti. I quattro pixel che costituiscono il confine principale vengono esaminati per vedere quale azione dovrebbe essere intrapresa. Il pittore potrebbe trovarsi in una delle diverse condizioni:
- Tutti e quattro i pixel di contorno sono riempiti.
- Tre dei pixel di confine sono riempiti.
- Due dei pixel di confine sono riempiti.
- Un pixel di confine è riempito.
- I pixel del limite zero vengono riempiti.
Quando si deve seguire un percorso o un confine, viene utilizzata la regola della mano destra. Il pittore segue la regione appoggiando la mano destra sul muro (il confine della regione) e avanzando lungo il bordo della regione senza togliere la mano.
Per il caso n. 1, il pittore dipinge (riempie) il pixel su cui si trova il pittore e interrompe l'algoritmo.
Per il caso n. 2 esiste un percorso che porta fuori dall'area. Dipingi il pixel su cui si trova il pittore e muoviti nella direzione del percorso aperto.
Per il caso n. 3, i due pixel di confine definiscono un percorso che, se abbiamo dipinto il pixel corrente, potrebbe impedirci di tornare dall'altra parte del percorso. Abbiamo bisogno di un "segno" per definire dove siamo e in quale direzione stiamo andando per vedere se torniamo mai esattamente allo stesso pixel. Se abbiamo già creato un tale "segno", conserviamo il nostro segno precedente e ci spostiamo al pixel successivo seguendo la regola della mano destra.
Viene utilizzato un segno per il primo confine di 2 pixel che si incontra per ricordare dove è iniziato il passaggio e in quale direzione si stava muovendo il pittore. Se si incontra di nuovo il segno e il pittore sta viaggiando nella stessa direzione, allora il pittore sa che è sicuro dipingere il quadrato con il segno e continuare nella stessa direzione. Questo perché (attraverso un percorso sconosciuto) i pixel dall'altra parte del segno possono essere raggiunti e dipinti in futuro. Il marchio viene rimosso per un uso futuro.
Se il pittore incontra il segno ma sta andando in una direzione diversa, si è verificata una sorta di loop che ha fatto sì che il pittore tornasse al segno. Questo ciclo deve essere eliminato. Il segno viene prelevato, e il pittore procede quindi nella direzione indicata in precedenza dal segno utilizzando un regolo sinistro per il confine (simile al regolo destro ma usando la mano sinistra del pittore). Questo continua finché non viene trovata un'intersezione (con tre o più pixel di confine aperti). Sempre usando la regola della mano sinistra il pittore ora cerca un passaggio semplice (composto da due pixel di confine). Dopo aver trovato questo percorso di confine di due pixel, quel pixel viene dipinto. Questo interrompe il ciclo e consente all'algoritmo di continuare.
Per il caso n. 4, dobbiamo controllare gli 8 angoli collegati opposti per vedere se sono pieni o meno. Se uno o entrambi sono riempiti, questo crea un'intersezione a molti percorsi e non può essere riempito. Se entrambi sono vuoti, il pixel corrente può essere dipinto e il pittore può muoversi seguendo la regola della mano destra.
L'algoritmo scambia il tempo per la memoria. Per le forme semplici è molto efficiente. Tuttavia, se la forma è complessa con molte caratteristiche, l'algoritmo impiega molto tempo a tracciare i bordi della regione cercando di assicurarsi che tutto possa essere dipinto.
Questo algoritmo è stato disponibile per la prima volta in commercio nel 1981 su un sistema di elaborazione delle immagini Vicom prodotto da Vicom Systems, Inc. Un algoritmo di camminata è stato pubblicato nel 1994. Il classico algoritmo di riempimento ricorsivo era disponibile anche sul sistema Vicom.
Pseudocodice
Questa è un'implementazione in pseudocodice di un algoritmo di riempimento del flusso a memoria fissa ottimale scritto in inglese strutturato:
- Le variabili
-
cur
,mark
, emark2
ciascuno contiene coordinate in pixel o un valore nullo- NOTA: quando
mark
è impostato su null, non cancellare il suo precedente valore della coordinata. Tieni a disposizione quelle coordinate per essere richiamate se necessario.
- NOTA: quando
-
cur-dir
,mark-dir
, emark2-dir
ognuno tiene una direzione (sinistra, destra, su o giù) -
backtrack
efindloop
ciascuno contiene valori booleani -
count
è un intero
- L'algoritmo
- NOTA: tutte le direzioni (anteriore, posteriore, sinistra, destra) sono relative a
cur-dir
set cur to starting pixel set cur-dir to default direction clear mark and mark2 (set values to null) set backtrack and findloop to false while front-pixel is empty do move forward end while jump to START MAIN LOOP: move forward if right-pixel is inside then if backtrack is true and findloop is false and either front-pixel or left-pixel is inside then set findloop to true end if turn right PAINT: move forward end if START: set count to number of non-diagonally adjacent pixels filled (front/back/left/right ONLY) if count is not 4 then do turn right while front-pixel is inside do turn left while front-pixel is not inside end if switch count case 1 if backtrack is true then set findloop to true else if findloop is true then if mark is null then restore mark end if else if front-left-pixel and back-left-pixel are both inside then clear mark set cur jump to PAINT end if end case case 2 if back-pixel is not inside then if front-left-pixel is inside then clear mark set cur jump to PAINT end if else if mark is not set then set mark to cur set mark-dir to cur-dir clear mark2 set findloop and backtrack to false else if mark2 is not set then if cur is at mark then if cur-dir is the same as mark-dir then clear mark turn around set cur jump to PAINT else set backtrack to true set findloop to false set cur-dir to mark-dir end if else if findloop is true then set mark2 to cur set mark2-dir to cur-dir end if else if cur is at mark then set cur to mark2 set cur-dir to mark2-dir clear mark and mark2 set backtrack to false turn around set cur jump to PAINT else if cur at mark2 then set mark to cur set cur-dir and mark-dir to mark2-dir clear mark2 end if end if end if end case case 3 clear mark set cur jump to PAINT end case case 4 set cur done end case end switch end MAIN LOOP
Vantaggi
- Utilizzo costante della memoria.
Svantaggi
- Il modello di accesso non è compatibile con la cache o il bitplane.
- Può passare molto tempo a girare intorno ai loop prima di chiuderli.
Implementazioni vettoriali
La versione 0.46 di Inkscape include uno strumento di riempimento del secchio, che fornisce un output simile alle normali operazioni bitmap e in effetti ne utilizza uno: viene eseguito il rendering del canvas, viene eseguita un'operazione di riempimento dell'area selezionata e il risultato viene quindi ricondotto a un tracciato. Utilizza il concetto di condizione al contorno .
Guarda anche
- Ricerca in ampiezza
- Ricerca in profondità
- Attraversamento grafico
- Etichettatura dei componenti collegati
- Algoritmo di Dijkstra
link esterno
- Implementazioni di esempio per flood fill ricorsivo e non ricorsivo, classico e scanline , di Lode Vandevenne.
- Esempio di implementazione Java utilizzando Q non ricorsivo.
Riferimenti
- ^ a b c Smith, Alvy Ray (1979). Riempimento tinta . SIGGRAPH '79: Atti del VI Convegno annuale su Computer grafica e tecniche interattive. pp. 276-283. doi : 10.1145/800249.807456 .
- ^ a b Ackland, Bryan D; Weste, Neil H (1981). L'algoritmo edge flag — Un metodo di riempimento per le visualizzazioni di scansione raster . Transazioni IEEE sui computer (Volume: C-30, Numero: 1). pp. 41-48. doi : 10.1109/TC.1981.6312155 .
- ^ a b c d e f g h i j Fishkin, Kenneth P; Barsky, Brian A (1985). Un'analisi e un algoritmo per la propagazione del riempimento . Immagini generate al computer: lo stato dell'arte degli atti dell'interfaccia grafica '85. pp. 56-76. doi : 10.1007/978-4-431-68033-8_6 .
- ^ Newman, William M; Sproull, Robert Fletcher (1979). Principi di computer grafica interattiva (2a ed.). McGraw Hill. P. 253. ISBN 978-0-07-046338-7.
- ^ Pavlidis, Theo (1982). Algoritmi per la grafica e l'elaborazione delle immagini . Springer-Verlag. P. 181. ISBN 978-3-642-93210-6.
- ^ a b c d e f g h i Levoy, Marc (1982). Algoritmi di inondazione dell'area . SIGGRAPH 1981 Appunti del corso di animazione computerizzata bidimensionale.
- ^ Foley, JD; van Dam, A; Feiner, SK; Hughes, SK (1990). Computer grafica: principi e pratica (2a ed.). Addison-Wesley. pp. 979–982. ISBN 978-0-201-84840-3.
- ^ Heckbert, Paul S (1990). "IV.10: Un algoritmo di riempimento del seme". In Glassner, Andrew S (ed.). Gemme grafiche . stampa accademica. pp. 275-277. ISBN 0122861663.
- ^ a b Lieberman, Henry (1978). Come colorare in un libro da colorare . SIGGRAPH '78: Atti del V Convegno annuale su Computer grafica e tecniche interattive. pp. 111-116. doi : 10.1145/800248.807380 .
- ^ a b c Shani, Uri (1980). Regioni di riempimento in immagini raster binarie: un approccio teorico dei grafi . SIGGRAPH '80: Atti del VII convegno annuale su Computer grafica e tecniche interattive. pp. 321-327. doi : 10.1145/800250.807511 .
- ^ a b Pavlidis, Theo (1981). Riempimento del contorno in grafica raster . SIGGRAPH '81: Atti dell'VIII convegno annuale su Computer grafica e tecniche interattive. pp. 29-36. doi : 10.1145/800224.806786 .
- ^ Henrich, Dominik (1994). Riempimento di aree efficienti in termini di spazio nella grafica raster . Il computer visivo. pp. 205-215. doi : 10.1007/BF01901287 .