Relleno de inundación - Flood fill
El relleno por inundación , también llamado relleno semilla , es un algoritmo que determina y altera el área conectada a un nodo dado en una matriz multidimensional con algún atributo coincidente. Se utiliza en la herramienta de relleno "cubo" de los programas de pintura para rellenar áreas conectadas de colores similares con un color diferente, y en juegos como Go y Minesweeper para determinar qué piezas se eliminan. Una variante llamada relleno de límite usa los mismos algoritmos pero se define como el área conectada a un nodo dado que no tiene un atributo particular.
Tenga en cuenta que el relleno de inundación no es adecuado para dibujar polígonos rellenos, ya que perderá algunos píxeles en las esquinas más agudas. En su lugar, consulte Regla par impar y Regla distinta de cero .
Los parámetros del algoritmo
El algoritmo tradicional de relleno por inundación toma tres parámetros: un nodo de inicio, un color de destino y un color de reemplazo. El algoritmo busca todos los nodos en la matriz que están conectados al nodo de inicio por una ruta del color de destino y los cambia al color de reemplazo. Para un relleno de límite, en lugar del color de destino, se proporcionaría un color de borde.
Para generalizar el algoritmo de la manera común, las siguientes descripciones tendrán en su lugar dos rutinas disponibles. Uno llamado Inside
que devuelve verdadero para puntos sin completar que, por su color, estarían dentro del área llena, y uno llamado Set
que llena un píxel / nodo. Cualquier nodo que lo haya Set
llamado debe dejar de serlo Inside
.
Dependiendo de si consideramos que los nodos se tocan en las esquinas conectados o no, tenemos dos variaciones: ocho y cuatro direcciones respectivamente.
Implementación recursiva basada en pilas (cuatro vías)
La implementación más antigua conocida, implícitamente basada en pila, recursiva y de relleno por inundación de cuatro vías es la siguiente:
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.
Aunque es fácil de entender, la implementación del algoritmo utilizado anteriormente no es práctica en lenguajes y entornos donde el espacio de la pila está severamente restringido (por ejemplo, microcontroladores ).
Mover la recursividad a una estructura de datos
Mover la recursividad a una estructura de datos (ya sea una pila o una cola ) evita un desbordamiento de la pila. Es similar a la solución recursiva simple, excepto que en lugar de hacer llamadas recursivas, empuja los nodos a una pila o cola para su consumo, y la elección de la estructura de datos afecta el patrón de proliferación:
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.
Posibles optimizaciones adicionales
- Verifique y establezca el color de píxel de cada nodo antes de agregarlo a la pila / cola, reduciendo el tamaño de la pila / cola.
- Utilice un bucle para las direcciones este / oeste, poniendo en cola los píxeles arriba / abajo a medida que avanza. (Haciéndolo similar a los algoritmos de llenado de tramo, a continuación).
- Intercalar dos o más copias del código con pilas / colas adicionales, para permitir que los procesadores OoO tengan más oportunidades de paralelizar
- Use varios hilos (idealmente con órdenes de visita ligeramente diferentes, para que no se queden en la misma área)
Ventajas
- Algoritmo muy simple: fácil de hacer sin errores.
Desventajas
- Utiliza mucha memoria, especialmente cuando se usa una pila.
- Prueba la mayoría de los píxeles llenos un total de cuatro veces.
- No es adecuado para el relleno de patrones, ya que requiere que cambien los resultados de la prueba de píxeles.
- El patrón de acceso no es compatible con la memoria caché, para la variante de cola.
- No se puede optimizar fácilmente para palabras o planos de bits de varios píxeles.
Llenado de tramo
Es posible optimizar aún más las cosas trabajando principalmente con tramos. El primer ejemplo completo publicado funciona según el siguiente principio básico. Comenzando con un punto de semilla, rellena a la izquierda y a la derecha y realiza un seguimiento de los bordes. Luego, escanea la misma parte de la línea de arriba y la línea de abajo, buscando nuevos puntos de semilla para continuar. Este algoritmo es el más popular, tanto para citas como para implementaciones, a pesar de probar la mayoría de los píxeles llenos tres veces en total. En forma de pseudocódigo:
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
Con el tiempo, se realizaron las siguientes optimizaciones:
- Cuando un nuevo escaneo estaría completamente dentro de un intervalo de abuelos, ciertamente solo encontraría píxeles llenos, por lo que no necesitaría hacer cola.
- Además, cuando un nuevo escaneo se superpone a un tramo de abuelos, solo es necesario escanear los voladizos (giros en U y giros en W).
- Es posible llenar mientras se escanea en busca de semillas
El relleno de tramo final combinado de escaneo y relleno se publicó en 1990 y procede de la siguiente manera (aunque la versión aquí corrige algunos errores en el original):
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
Ventajas
- 2 x 8 veces más rápido que el algoritmo recursivo de píxeles.
- El patrón de acceso es compatible con la memoria caché y el plano de bits.
- Puede dibujar una línea horizontal en lugar de establecer píxeles individuales.
Desventajas
- Todavía visita los píxeles que ya ha llenado. (Para el algoritmo popular, 3 escaneos de la mayoría de los píxeles. Para el último, solo se realizan escaneos adicionales de píxeles donde hay huecos en el área llena).
- No es adecuado para el relleno de patrones, ya que requiere que cambien los resultados de la prueba de píxeles.
Adición de compatibilidad con relleno de patrones
Dos formas comunes de hacer que los algoritmos basados en píxeles y en tramos sean compatibles con el relleno de patrones son usar un color único como relleno simple y luego reemplazarlo con un patrón o realizar un seguimiento (en una matriz booleana 2d o como regiones) de qué píxeles se han visitado, utilizándolo para indicar que los píxeles ya no se pueden rellenar. El interior debe devolver falso para dichos píxeles visitados.
Relleno teórico de gráficos
Algunos teóricos aplicaron la teoría de grafos explícita al problema, tratando tramos de píxeles, o agregados de los mismos, como nodos y estudiando su conectividad. El primer algoritmo de teoría de grafos publicado funcionó de manera similar al relleno de tramos, arriba, pero tenía una forma de detectar cuándo duplicaría el llenado de tramos. Desafortunadamente, tenía errores que hicieron que no completara algunos rellenos. Posteriormente se publicó un algoritmo corregido con una base similar en la teoría de grafos; sin embargo, altera la imagen a medida que avanza, para bloquear temporalmente posibles bucles, lo que complica la interfaz programática. Un algoritmo publicado posteriormente dependía de que el límite fuera distinto de todo lo demás en la imagen y, por lo tanto, no es adecuado para la mayoría de los usos; también requiere un bit extra por píxel para la contabilidad.
Ventajas
- Adecuado para el relleno de patrones, directamente, ya que nunca vuelve a probar los píxeles llenos.
- Duplique la velocidad del algoritmo de tramo original, para rellenos sin complicaciones.
- El patrón de acceso es compatible con la memoria caché y el plano de bits.
Desventajas
- Regularmente, un tramo debe compararse con cualquier otro "frente" en la cola, lo que ralentiza significativamente los rellenos complicados.
- El cambio entre la teoría de gráficos y los dominios de píxeles complica la comprensión.
- El código es bastante complicado, lo que aumenta las posibilidades de errores.
Llenado basado en caminar (método de memoria fija)
Existe un método que esencialmente no usa memoria para cuatro regiones conectadas , pretendiendo ser un pintor que intenta pintar la región sin pintarse a sí mismo en una esquina. Este también es un método para resolver laberintos. Los cuatro píxeles que forman el límite principal se examinan para ver qué acción se debe tomar. El pintor podría encontrarse en una de varias condiciones:
- Se rellenan los cuatro píxeles de los límites.
- Se rellenan tres de los píxeles de los límites.
- Se rellenan dos de los píxeles de los límites.
- Se rellena un píxel de límite.
- Se rellenan los píxeles de límite cero.
Cuando se va a seguir una ruta o un límite, se usa la regla de la mano derecha. El pintor sigue la región colocando su mano derecha en la pared (el límite de la región) y avanzando alrededor del borde de la región sin quitar la mano.
Para el caso n. ° 1, el pintor pinta (llena) el píxel sobre el que se encuentra el pintor y detiene el algoritmo.
Para el caso n. ° 2, existe un camino que sale del área. Pinte el píxel sobre el que está parado el pintor y muévase en la dirección del camino abierto.
Para el caso n. ° 3, los dos píxeles de los límites definen una ruta que, si pintamos el píxel actual, puede impedirnos volver al otro lado de la ruta. Necesitamos una "marca" para definir dónde estamos y en qué dirección nos dirigimos para ver si alguna vez volvemos exactamente al mismo píxel. Si ya creamos tal "marca", entonces conservamos nuestra marca anterior y pasamos al siguiente píxel siguiendo la regla de la mano derecha.
Se utiliza una marca para el primer límite de 2 píxeles que se encuentra para recordar dónde comenzó el pasaje y en qué dirección se movía el pintor. Si la marca se encuentra de nuevo y el pintor viaja en la misma dirección, entonces el pintor sabe que es seguro pintar el cuadrado con la marca y continuar en la misma dirección. Esto se debe a que (a través de alguna ruta desconocida) los píxeles del otro lado de la marca se pueden alcanzar y pintar en el futuro. La marca se elimina para uso futuro.
Si el pintor encuentra la marca pero va en una dirección diferente, entonces se ha producido algún tipo de bucle, lo que hizo que el pintor volviera a la marca. Este bucle debe eliminarse. Se toma la marca y el pintor procede en la dirección indicada previamente por la marca utilizando una regla de la mano izquierda para el límite (similar a la regla de la mano derecha pero usando la mano izquierda del pintor). Esto continúa hasta que se encuentra una intersección (con tres o más píxeles de límite abiertos). Aún utilizando la regla de la mano izquierda, el pintor busca ahora un pasaje simple (formado por dos píxeles de límite). Al encontrar esta ruta de límite de dos píxeles, ese píxel está pintado. Esto rompe el ciclo y permite que el algoritmo continúe.
Para el caso n. ° 4, debemos verificar las 8 esquinas opuestas conectadas para ver si están llenas o no. Si se rellena uno o ambos, esto crea una intersección de muchos caminos y no se puede rellenar. Si ambos están vacíos, el píxel actual se puede pintar y el pintor puede moverse siguiendo la regla de la derecha.
El algoritmo intercambia tiempo por memoria. Para formas simples es muy eficiente. Sin embargo, si la forma es compleja con muchas características, el algoritmo dedica una gran cantidad de tiempo a trazar los bordes de la región para asegurarse de que todo se pueda pintar.
Este algoritmo estuvo disponible comercialmente por primera vez en 1981 en un sistema de procesamiento de imágenes Vicom fabricado por Vicom Systems, Inc. En 1994 se publicó un algoritmo de desplazamiento. El algoritmo clásico de relleno de inundación recursivo también estaba disponible en el sistema Vicom.
Pseudocódigo
Esta es una implementación de pseudocódigo de un algoritmo óptimo de relleno por inundación de memoria fija escrito en inglés estructurado:
- Las variables
-
cur
,mark
ymark2
cada uno contiene coordenadas de píxeles o un valor nulo- NOTA: cuando
mark
se establece en nulo, no borre su valor de coordenada anterior. Mantenga esas coordenadas disponibles para recordarlas si es necesario.
- NOTA: cuando
-
cur-dir
,mark-dir
ymark2-dir
cada uno tiene una dirección (izquierda, derecha, arriba o abajo) -
backtrack
yfindloop
cada uno tiene valores booleanos -
count
es un entero
- El algoritmo
- NOTA: Todas las direcciones (frontal, posterior, izquierda, derecha) son relativas 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
Ventajas
- Uso constante de memoria.
Desventajas
- El patrón de acceso no es compatible con la memoria caché ni con el plano de bits.
- Puede pasar mucho tiempo dando vueltas alrededor de los bucles antes de cerrarlos.
Implementaciones vectoriales
La versión 0.46 de Inkscape incluye una herramienta de llenado de cubos, que da un resultado similar a las operaciones de mapa de bits ordinarias y, de hecho, usa una: se renderiza el lienzo, se realiza una operación de llenado de inundación en el área seleccionada y el resultado luego se rastrea hasta una ruta. Utiliza el concepto de condición de contorno .
Ver también
- Búsqueda en amplitud primero
- Búsqueda en profundidad
- Recorrido del gráfico
- Etiquetado de componentes conectados
- Algoritmo de Dijkstra
enlaces externos
- Implementaciones de muestra para relleno de inundación recursivo y no recursivo, clásico y de línea de exploración , por Lode Vandevenne.
- Ejemplo de implementación de Java usando Q no recursivo.
Referencias
- ↑ a b c Smith, Alvy Ray (1979). Relleno de tinte . SIGGRAPH '79: Actas de la 6ª conferencia anual sobre gráficos por ordenador y técnicas interactivas. págs. 276-283. doi : 10.1145 / 800249.807456 .
- ↑ a b Ackland, Bryan D; Weste, Neil H (1981). El algoritmo de la bandera de borde: un método de relleno para pantallas de escaneo ráster . Transacciones IEEE en computadoras (Volumen: C-30, Edición: 1). págs. 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 análisis y algoritmo para la propagación de relleno . Imágenes generadas por computadora: los procedimientos de vanguardia de la interfaz gráfica '85. págs. 56–76. doi : 10.1007 / 978-4-431-68033-8_6 .
- ^ Newman, William M; Sproull, Robert Fletcher (1979). Principios de gráficos interactivos por computadora (2ª ed.). McGraw-Hill. pag. 253. ISBN 978-0-07-046338-7.
- ^ Pavlidis, Theo (1982). Algoritmos para procesamiento de imágenes y gráficos . Springer-Verlag. pag. 181. ISBN 978-3-642-93210-6.
- ↑ a b c d e f g h i Levoy, Marc (1982). Algoritmos de inundación de área . SIGGRAPH 1981 Notas del curso de animación por computadora bidimensional.
- ^ Foley, JD; van Dam, A; Feiner, SK; Hughes, SK (1990). Gráficos por computadora: principios y práctica (2ª ed.). Addison – Wesley. págs. 979–982. ISBN 978-0-201-84840-3.
- ^ Heckbert, Paul S (1990). "IV.10: Un algoritmo de relleno de semillas". En Glassner, Andrew S (ed.). Gemas de gráficos . Prensa académica. págs. 275-277. ISBN 0122861663.
- ↑ a b Lieberman, Henry (1978). Cómo colorear un libro para colorear . SIGGRAPH '78: Actas de la 5ª conferencia anual sobre gráficos por ordenador y técnicas interactivas. págs. 111-116. doi : 10.1145 / 800248.807380 .
- ↑ a b c Shani, Uri (1980). Relleno de regiones en imágenes ráster binarias: un enfoque de teoría de gráficos . SIGGRAPH '80: Actas de la 7ª conferencia anual sobre gráficos por ordenador y técnicas interactivas. págs. 321–327. doi : 10.1145 / 800250.807511 .
- ↑ a b Pavlidis, Theo (1981). Relleno de contorno en gráficos de trama . SIGGRAPH '81: Actas de la 8ª conferencia anual sobre gráficos por ordenador y técnicas interactivas. págs. 29–36. doi : 10.1145 / 800224.806786 .
- ^ Henrich, Dominik (1994). Relleno de regiones con uso eficiente del espacio en gráficos rasterizados . La computadora visual. págs. 205–215. doi : 10.1007 / BF01901287 .