Работающий нерекурсивный алгоритм заливки, написанный на C?
Я пытаюсь найти рабочий алгоритм заливки. Из многих алгоритмов, которые я пробовал только "рекурсивную заливку строк", каждый ведет себя точно так же, как должен, с основным предостережением, которое он иногда ударяет по стеклу.: (
Я пробовал много нерекурсивных реализаций, которые я нашел, и все они были исключительно закалимыми: либо они оставляют пробелы в странных местах, либо наводняют всю область (когда они должны быть заключены).
У кого-то есть нерекурсивный рабочий код наводнения, написанный на C (или С++, который не слишком сильно ооо, и я могу распутать достаточно легко)?
Ответы
Ответ 1
Вот код С++, который делает то, что вы хотите. Он использует очередь и более эффективен при вставках в очередь.
connectedRegion(const Point& source, RegionType& region, const Color target)
{
Color src_color = color_of(source, region);
if (region.count(source) == 0 || src_color == target)
return;
std::queue<Point> analyze_queue;
analyze_queue.push(source);
while (!analyze_queue.empty())
{
if (color_of(analyze_queue.front()) != src_color)
{
analyze_queue.pop();
continue;
}
Point leftmost_pt = analyze_queue.front();
leftmost_pt.col -= 1;
analyze_queue.pop();
Point rightmost_pt = leftmost_pt;
rightmost_pt.col += 2;
while (color_of(leftmost_pt, region) == src_color)
--leftmost_pt.col;
while (color_of(rightmost_pt, region) == src_color)
++rightmost_pt.col;
bool check_above = true;
bool check_below = true;
Point pt = leftmost_pt;
++pt.col;
for (; pt.col < rightmost_pt.col; ++pt.col)
{
set_color(pt, region, target);
Point pt_above = pt;
--pt_above.row;
if (check_above)
{
if (color_of(pt_above, region) == src_color)
{
analyze_queue.push(pt_above);
check_above = false;
}
}
else // !check_above
{
check_above = (color_of(pt_above, region) != src_color);
}
Point pt_below = pt;
++pt_below.row;
if (check_below)
{
if (color_of(pt_below, region) == src_color)
{
analyze_queue.push(pt_below);
check_below = false;
}
}
else // !check_below
{
check_below = (color_of(pt_below, region) != src_color);
}
} // for
} // while queue not empty
return connected;
}
Ответ 2
Просто реализуйте стек пар int с массивом некоторого фиксированного размера (возможно, размером изображения в пикселях или квадратным корнем из этого, например) для стека и отслеживайте вершину с помощью int.
Вот несколько С# -кодов, которые не рекурсивно реализуют floodfill:
private static void Floodfill(byte[,] vals, Point q, byte SEED_COLOR, byte COLOR)
{
int h = vals.GetLength(0);
int w = vals.GetLength(1);
if (q.Y < 0 || q.Y > h - 1 || q.X < 0 || q.X > w - 1)
return;
Stack<Point> stack = new Stack<Point>();
stack.Push(q);
while (stack.Count > 0)
{
Point p = stack.Pop();
int x = p.X;
int y = p.Y;
if (y < 0 || y > h - 1 || x < 0 || x > w - 1)
continue;
byte val = vals[y, x];
if (val == SEED_COLOR)
{
vals[y, x] = COLOR;
stack.Push(new Point(x + 1, y));
stack.Push(new Point(x - 1, y));
stack.Push(new Point(x, y + 1));
stack.Push(new Point(x, y - 1));
}
}
}
Ответ 3
Быстрый поиск в googling вызывает статью Википедии о Flood Fill, которая включает в себя реализации псевдокода, которые не являются рекурсивными. Ниже приведен какой-то код, который может помочь вам начать работу с базовой реализацией очереди в C:
typedef struct queue_ { struct queue_ *next; } queue_t;
typedef struct ffnode_ { queue_t node; int x, y; } ffnode_t;
/* returns the new head of the queue after adding node to the queue */
queue_t* enqueue(queue_t *queue, queue_t *node) {
if (node) {
node->next = queue;
return node;
}
return NULL;
}
/* returns the head of the queue and modifies queue to be the new head */
queue_t* dequeue(queue_t **queue) {
if (queue) {
queue_t *node = (*queue);
(*queue) = node->next;
node->next = NULL;
return node;
}
return NULL;
}
ffnode_t* new_ffnode(int x, int y) {
ffnode_t *node = (ffnode_t*)malloc(sizeof(ffnode_t));
node->x = x; node->y = y;
node->node.next = NULL;
return node;
}
void flood_fill(image_t *image, int startx, int starty,
color_t target, color_t replacement) {
queue_t *head = NULL;
ffnode_t *node = NULL;
if (!is_color(image, startx, starty, target)) return;
node = new_ffnode(startx, starty);
for ( ; node != NULL; node = (ffnode_t*)dequeue(&head)) {
if (is_color(image, node->x, node->y, target)) {
ffnode_t *west = node, *east = node;
recolor(image, node->x, node->y, replacement);
/* 1. move w to the west until the color of the node to the west
no longer matches target */
...
}
}
}
Ответ 4
Нет ли там где-либо доказательства, что все рекурсивные функции могут быть реализованы как итеративная функция, используя локальные данные для имитации стека? Вероятно, вы можете использовать std::vector для создания стекового поведения алгоритма без выдувания стека, так как он будет использовать кучу.
EDIT: я заметил, что вы используете C, поэтому вместо std::vector вы можете просто реализовать подобное поведение через realloc, поскольку вам нужно добавить больше элементов в свой локальный "стек" любой структуры данных, которую вы будете использовать.
Ответ 5
Вы можете преобразовать любой рекурсивный алгоритм в итеративный, создав явный стек или очередь и загрузив на него работу/вытащив ее.
Все, что вам нужно, - это выбрать хорошее, компактное представление работы. Худший случай: создайте struct
, содержащий аргументы, которые вы обычно передадите в рекурсивную версию...
Ответ 6
У меня есть нерекурсивная заливка заливки, но я не буду публиковать ее, потому что это решение для домашнего задания. Но здесь намек: поиск по глубине, который является естественным алгоритмом, использует гораздо больше вспомогательного пространства, чем поиск по ширине. Вот то, что я написал в то время (соответственно изъято):
Я не осмеливаюсь пробовать поиск по глубине простой рекурсией; глубина рекурсии ограничена только REDACTED, и мои эксперименты показывают, что ПРОБЛЕМА REDACTED может, тем не менее, потребовать глубину стека более миллиона. Поэтому я помещаю стек во вспомогательную структуру данных. Использование явного стека на самом деле позволяет легко попробовать поиск по ширине, и оказывается, что поиск в ширину может использовать в сорок раз меньше места, чем поиск по глубине.
Для моей структуры данных я использовал Seq_T
от Dave Hanson C Интерфейсы и реализации; изменение с глубины - сначала на ширину - сначала требует изменения только одного вызова функции.
Ответ 7
Мы заметили, что наша реализация наводнения на трехмерных томах поглощала много памяти; поэтому мы изменили код следующим образом (было значительное улучшение):
-
Создайте сферу радиуса = 10 vox вокруг начальной точки и отметьте все вокселы в этом радиусе как "для посещения"
-
Если текущий порог voxel > , вставьте 1.
-
Перейдите к соседям [+1, -1, 0] (также убедитесь, что никто не пересматривает какой-либо воксел), если neighbour.getVoxVal = 0 (значение инициализации для целевого тома), тогда он падает на границу сферы, вставляют координаты в другой стек. (это будет отправной точкой для нашей следующей сферы)
-
радиус = радиус + 10 (вокселы)
Итак, в то время наша заливка работает над концентрической сферой и наполняет ее, что является частью всего объема, и, как я уже сказал, это значительно сократило потребление памяти, но я все еще ищу реализация/идея, которая была бы лучше.
Ответ 8
ДА: Это самое простое и самое низкое решение для ОЗУ для кубической линии сканирования... оно сканирует и заполняет куб только в одном цикле 3D-пространства! Каждый раз, когда этот алгоритм запускается, он заполняет все пробелы слева и справа от первого пикселя за один проход.
Он может заливать 2 миллиона вокселов в секунду
Объем памяти 4 ГБ для 1 млрд. пространства вокселей (общая версия стека списков, которую я попытался разбивать после 12 ГБ в плунжерах из-за чрезмерного разветвления, стек стека по умолчанию разбился после 10 млн. вокселей)
Очень легко кодировать и понимать (40 строк кода, включая объявления переменных), проще, чем руководства и руководства, которые я нашел в Интернете.
метод:
Создайте свой собственный логический блок виртуальной оперативной памяти вместо использования стеков.
Использование булевого массива для хранения позиций проверяемых вокселей/пикселей. он более эффективен при вызове функций, и он быстрее для больших пространств и может быть объединен с версией стека, чтобы закончить последние 1-10 миллионов заполнений, которые извлекают выгоду из линейной памяти в массиве voxel типа 1 миллиард.
Почему это так здорово?
рекурсия автоматически становится алгоритмом сканирования: вы можете проверять пустое пространство линейно через блок памяти... шесть поисковых запросов просматриваются вокруг каждого проверочного пробела, поэтому вы отмечаете в памяти, что пространство нужно искать перед текущий пиксель. он волшебным образом становится функцией линии сканирования, которая может заполнять 100 см миллионов вокселей в одном цикле поиска через RAM! Цикл поиска из миллиарда булевых массивов занимает 5 секунд на ПК с генератором i7, что быстро! вычислительно интенсивная вещь сравнивается с тем, что истинно и ложно.
Полное описание и код здесь: https://unity3dmc.blogspot.fr/2017/02/ultimate-3d-floodfill-scanline.html
Ответ 9
Ниже приведен мой итеративный метод С++ на основе BFS для проблемы заполнения заливки:
// M is the input matrix, with every entry(pixel) have a color
// represented with an integer value.
// (x, y) row and column of seed point respectively
// k: The new color to fill the seed and its adjacent pixels
void floodFill(vector<vector<int>> &M, int x, int y, int k) {
queue<pair<int, int>> nodeQ;
nodeQ.push({x, y});
int oldCol = M[x][y];
while(!nodeQ.empty()) {
pair<int, int> currNode = nodeQ.front();
nodeQ.pop();
if(M[currNode.first][currNode.second] == oldCol) {
M[currNode.first][currNode.second] = k;
if(currNode.first > 0) nodeQ.push({currNode.first-1, currNode.second});
if(currNode.first < (M.size()-1)) nodeQ.push({currNode.first+1, currNode.second});
if(currNode.second > 0) nodeQ.push({currNode.first, currNode.second-1});
if(currNode.second < (M[0].size()-1)) nodeQ.push({currNode.first, currNode.second+1});
}
}
}
Ответ 10
Я не знаю, подходит ли мой ответ на заданный вами вопрос, но в дальнейшем я предлагаю свою версию C алгоритма Flood-Fill, которая не использует рекурсивные вызовы.
1-11-2017: NEW-VERSION; УСПЕШНО ИСПЫТАЕТСЯ С ДВУМЯ БИТМАПЫ.
Он использует только очередь смещений новых точек, он работает в окне: WinnOffs- (WinDimX, WinDimY) двойного буфера: * VBuffer (копия экрана или изображения) и, необязательно, напишите маску результата наводнения (* ExtraVBuff).
ExtraVBuff должен быть заполнен им 0 перед вызовом (если вам не нужна маска, вы можете установить ExtraVBuff = NULL); используя его после вызова, вы можете выполнять заливку градиентом или другие эффекты окраски. NewFloodFill работает с 32 бит на пиксель, и это функция C. Я изобрел этот алгоритм в 1991 году (я написал его в Pascal), но теперь он работает на C с 32 бит на пиксель; также не использует вызовы функций, выполняет только разделение после каждой "поп" из очереди и никогда не переполняет очередь, что, если она имеет размер в правильном направлении (около 1/4 пикселей изображения), это позволяет всегда правильно заполнять любую область; Я показываю перед c-функцией (FFILL.C) после тестовой программы (TEST.C):
#define IMAGE_WIDTH 1024
#define IMAGE_HEIGHT 768
#define IMAGE_SIZE IMAGE_WIDTH*IMAGE_HEIGHT
#define QUEUE_MAX IMAGE_SIZE/4
typedef int T_Queue[QUEUE_MAX];
typedef int T_Image[IMAGE_SIZE];
void NewFloodFill(int X,
int Y,
int Color,
int BuffDimX,
int WinOffS,
int WinDimX,
int WinDimY,
T_Image VBuffer,
T_Image ExtraVBuff,
T_Queue MyQueue)
/* Replaces all pixels adjacent to the first pixel and equal to this; */
/* if ExtraVBuff == NULL writes to *VBuffer (eg BUFFER of 786432 Pixel),*/
/* otherwise prepare a mask by writing on *ExtraVBuff (such BUFFER must */
/* always have the same size as *VBuffer (it must be initialized to 0)).*/
/* X,Y: Point coordinates' of origin of the flood-fill. */
/* WinOffS: Writing start offset on *VBuffer and *ExtraVBuff. */
/* BuffDimX: Width, in number of Pixel (int), of each buffer. */
/* WinDimX: Width, in number of Pixel (int), of the window. */
/* Color: New color that replace all_Pixel == origin's_point. */
/* WinDimY: Height, in number of Pixel (int), of the window. */
/* VBuffer: Pointer to the primary buffer. */
/* ExtraVBuff: Pointer to the mask buffer (can be = NULL). */
/* MyQueue: Pointer to the queue, containing the new-points' offsets*/
{
int VBuffCurrOffs=WinOffS+X+Y*BuffDimX;
int PixelIn=VBuffer[VBuffCurrOffs];
int QueuePnt=0;
int *TempAddr=((ExtraVBuff) ? ExtraVBuff : VBuffer);
int TempOffs1;
int TempX1;
int TempX2;
char FLAG;
if (0<=X && X<WinDimX && 0<=Y && Y<WinDimY) do
{
/* Fill to left the current line */
TempX2=X;
while (X>=0 && PixelIn==VBuffer[VBuffCurrOffs])
{
TempAddr[VBuffCurrOffs--]=Color;
--X;
}
TempOffs1=VBuffCurrOffs+1;
TempX1=X+1;
/* Fill to right the current line */
VBuffCurrOffs+=TempX2-X;
X=TempX2;
while (X+1<WinDimX && PixelIn==VBuffer[VBuffCurrOffs+1])
{
++X;
TempAddr[++VBuffCurrOffs]=Color;
}
TempX2=X;
/* Backward scan of the previous line; puts new points offset in Queue[] */
if (Y>0)
{
FLAG=1;
VBuffCurrOffs-=BuffDimX;
while (X-->=TempX1)
{
if (PixelIn!=VBuffer[VBuffCurrOffs] ||
ExtraVBuff && Color==ExtraVBuff[VBuffCurrOffs])
FLAG=1;
else
if (FLAG)
{
FLAG=0;
if (QueuePnt<QUEUE_MAX)
MyQueue[QueuePnt++]=VBuffCurrOffs;
}
--VBuffCurrOffs;
}
}
/* Forward scan of the next line; puts new points offset in Queue[] */
if (Y<WinDimY-1)
{
FLAG=1;
VBuffCurrOffs=TempOffs1+BuffDimX;
X=TempX1;
while (X++<=TempX2)
{
if (PixelIn!=VBuffer[VBuffCurrOffs] ||
ExtraVBuff && Color==ExtraVBuff[VBuffCurrOffs])
FLAG=1;
else
if (FLAG)
{
FLAG=0;
if (QueuePnt<QUEUE_MAX)
MyQueue[QueuePnt++]=VBuffCurrOffs;
}
++VBuffCurrOffs;
}
}
/* Gets a new point offset from Queue[] */
if (--QueuePnt>=0)
{
VBuffCurrOffs=MyQueue[QueuePnt];
TempOffs1=VBuffCurrOffs-WinOffS;
X=TempOffs1%BuffDimX;
Y=TempOffs1/BuffDimX;
}
/* Repeat the main cycle until the Queue[] is not empty */
} while (QueuePnt>=0);
}
Здесь есть тестовая программа:
#include <stdio.h>
#include <malloc.h>
#include "ffill.c"
#define RED_COL 0xFFFF0000
#define WIN_LEFT 52
#define WIN_TOP 48
#define WIN_WIDTH 920
#define WIN_HEIGHT 672
#define START_LEFT 0
#define START_TOP 671
#define BMP_HEADER_SIZE 54
typedef char T_Image_Header[BMP_HEADER_SIZE];
void main(void)
{
T_Image_Header bmpheader;
T_Image *image;
T_Image *mask;
T_Queue *MyQueue;
FILE *stream;
char *filename1="ffill1.bmp";
char *filename2="ffill2.bmp";
char *filename3="ffill3.bmp";
int bwritten;
int bread;
image=malloc(sizeof(*image));
mask=malloc(sizeof(*mask));
MyQueue=malloc(sizeof(*MyQueue));
stream=fopen(filename1,"rb");
bread=fread(&bmpheader, 1, BMP_HEADER_SIZE, stream);
bread=fread((char *)image, 1, IMAGE_SIZE<<2, stream);
fclose(stream);
memset(mask,0,IMAGE_SIZE<<2);
NewFloodFill(START_LEFT,
START_TOP,
RED_COL,
IMAGE_WIDTH,
IMAGE_WIDTH*WIN_TOP+WIN_LEFT,
WIN_WIDTH,
WIN_HEIGHT,
*image,
NULL,
*MyQueue);
stream=fopen(filename2,"wb+");
bwritten=fwrite(&bmpheader, 1, BMP_HEADER_SIZE, stream);
bwritten=fwrite((char *)image, 1, IMAGE_SIZE<<2, stream);
fclose(stream);
stream=fopen(filename3,"wb+");
bwritten=fwrite(&bmpheader, 1, BMP_HEADER_SIZE, stream);
bwritten=fwrite((char *)mask, 1, IMAGE_SIZE<<2, stream);
fclose(stream);
free(MyQueue);
free(mask);
free(image);
}
Я использовал для ввода показанной тестовой программы следующее несжатое .BMP-изображение Windows (ffill1.bmp):
![введите описание изображения здесь]()
Заполняется, по показанной тестовой программе, следующим образом (ffill2.bmp):
![введите описание изображения здесь]()
Используя "маску" вместо NULL, выходное растровое изображение (ffill3.bmp):
![введите описание изображения здесь]()