1、首先介绍两个概念,就是“4-联通算法”和“8-联通算法”。既然是搜索就涉及到搜索的方向问题,从区域内任意一点出发,如果只是通过上、下、左、右四个方向搜索到达区域内的任意像素,则用这种方法填充的区域就称为四连通域,这种填充方法就称为“4-联通算法”。如果从区域内任意一点出发,通过上、下、左、右、左上、左下、右上和右下全部八个方向到达区域内的任意像素,则这种方法填充的区域就称为八连通域,这种填充方法就称为“8-联通算法”。下面引用几张网络上的图片,辅助说明:

填充算法(一)递归填充算法_算法实现

填充算法(一)递归填充算法_递归_02

填充算法(一)递归填充算法_mfc_03


2、下面以VS2010,MFC对话框工程为环境实现算法。


3、注入填充算法(Flood Fill Algorithm)

(1)介绍:注入填充算法不特别强调区域的边界,它只是从指定位置开始,将所有联通区域内某种指定颜色的点都替换成另一种颜色,从而实现填充效果。注入填充算法能够实现颜色替换之类的功能,这在图像处理软件中都得到了广泛的应用。注入填充算法的实现非常简单,核心就是递归和搜索。

(2)4联通算法实现1:


//************************************
// Parameter: CDC * pDC
// Parameter: int x (填充起始点的x坐标)
// Parameter: int y (填充起始点的y坐标)
// Parameter: COLORREF colorOld (被填充色)
// Parameter: COLORREF colorNew	(填充色)
//************************************
void FloodSeedFill(CDC* pDC, int x, int y, COLORREF colorOld, COLORREF colorNew)
{
	if (pDC->GetPixel( CPoint(x, y) ) == colorOld)
	{
		pDC->SetPixel( CPoint(x, y), colorNew);

		FloodSeedFill(pDC, x, y - 1, colorOld, colorNew);
		FloodSeedFill(pDC, x, y + 1, colorOld, colorNew);
		FloodSeedFill(pDC, x - 1, y, colorOld, colorNew);
		FloodSeedFill(pDC, x + 1, y, colorOld, colorNew);
	}
}



(3)4联通算法实现2(注:同实现1,只是写法不同):


typedef struct tagDIRECTION
{
	int x_offset;
	int y_offset;
}DIRECTION;
DIRECTION direction_4[] = { {-1, 0}, {0, 1}, {1, 0}, {0, -1} };
void FloodSeedFill(CDC* pDC, int x, int y, COLORREF colorOld, COLORREF colorNew)
{
	if (pDC->GetPixel( CPoint(x, y) ) == colorOld)
	{
		pDC->SetPixel( CPoint(x, y), colorNew);

		for(int i = 0; i < 4; i++)
		{
			FloodSeedFill(pDC, x + direction_4[i].x_offset, y + direction_4[i].y_offset, colorOld, colorNew);
		}
	}
}


(4)注: 上述两个算法实现理论上是没有错误的,但是在实际环境下,却会运行中断,具体原因是因为当递归深度过深时,栈溢出;而由递归深度引起的栈溢出(Stack overflow)是无解的(递归深度无法确定,栈大小修改不便),除非不用递归算法。附:修改工程栈大小:

(5)尝试减少递归深度,还是Stack Overflow,代码如下:

void FloodSeedFill(CDC* pDC, int x, int y, COLORREF colorOld, COLORREF colorNew, int nIgnore/* = 0*/)
{
	if (pDC->GetPixel( CPoint(x, y) ) == colorOld)
	{
		pDC->SetPixel( CPoint(x, y), colorNew);

		switch (nIgnore)
		{
		case 0:
			FloodSeedFill(pDC, x, y - 1, colorOld, colorNew, 1); // 上
			FloodSeedFill(pDC, x + 1, y, colorOld, colorNew, 2); // 右
			FloodSeedFill(pDC, x, y + 1, colorOld, colorNew, 3); // 下
			FloodSeedFill(pDC, x - 1, y, colorOld, colorNew, 4); // 左
			break;

		case 1:
			FloodSeedFill(pDC, x, y - 1, colorOld, colorNew, 1); // 上
			FloodSeedFill(pDC, x + 1, y, colorOld, colorNew, 2); // 右
			//FloodSeedFill(pDC, x, y + 1, colorOld, colorNew, 3); // 下
			FloodSeedFill(pDC, x - 1, y, colorOld, colorNew, 4); // 左
			break;

		case 2:
			FloodSeedFill(pDC, x, y - 1, colorOld, colorNew, 1); // 上
			FloodSeedFill(pDC, x + 1, y, colorOld, colorNew, 2); // 右
			FloodSeedFill(pDC, x, y + 1, colorOld, colorNew, 3); // 下
			//FloodSeedFill(pDC, x - 1, y, colorOld, colorNew, 4); // 左
			break;

		case 3:
			//FloodSeedFill(pDC, x, y - 1, colorOld, colorNew, 1); // 上
			FloodSeedFill(pDC, x + 1, y, colorOld, colorNew, 2); // 右
			FloodSeedFill(pDC, x, y + 1, colorOld, colorNew, 3); // 下
			FloodSeedFill(pDC, x - 1, y, colorOld, colorNew, 4); // 左
			break;

		case 4:
			FloodSeedFill(pDC, x, y - 1, colorOld, colorNew, 1); // 上
			//FloodSeedFill(pDC, x + 1, y, colorOld, colorNew, 2); // 右
			FloodSeedFill(pDC, x, y + 1, colorOld, colorNew, 3); // 下
			FloodSeedFill(pDC, x - 1, y, colorOld, colorNew, 4); // 左
			break;

		default:
			break;
		}
	}
}




(6)上述两个实现都是4联通的,8联通只是多加几行代码,这里就不再累述了;


DIRECTION direction_8[] = { {-1, 0}, {-1, 1}, {0, 1}, {1, 1}, {1, 0}, {1, -1}, {0, -1}, {-1, -1} };


4、边界填充算法(Boundary Fill Algorithm)

(1)介绍:边界填充算法与注入填充算法的本质其实是一样的,都是递归和搜索,区别只在于对边界的确认,也就是递归的结束条件不一样。注入填充算法没有边界的概念,只是对联通区域内指定的颜色进行替换,而边界填充算法恰恰强调边界的存在,只要是边界内的点无论是什么颜色,都替换成指定的颜色。边界填充算法在应用上也非常的广泛,画图软件中的“油漆桶”功能就是边界填充算法的例子。

(2)4通道算法实现:


void BoundarySeedFill(CDC* pDC, int x, int y, COLORREF colorBoundary, COLORREF colorNew)
{
	COLORREF colorCurrent = pDC->GetPixel( CPoint(x, y) );
	if (colorCurrent != colorBoundary && colorCurrent != colorNew)
	{
		pDC->SetPixel( CPoint(x, y), colorNew);

		BoundarySeedFill(pDC, x, y - 1, colorBoundary, colorNew);
		BoundarySeedFill(pDC, x, y + 1, colorBoundary, colorNew);
		BoundarySeedFill(pDC, x - 1, y, colorBoundary, colorNew);
		BoundarySeedFill(pDC, x + 1, y, colorBoundary, colorNew);
	}
}




5、两种(注入和边界)填充方法的区别:

(1)注入填充算法(替换相连像素点的颜色):

填充算法(一)递归填充算法_mfc_04

填充前


填充算法(一)递归填充算法_算法实现_05

填充过程1


填充算法(一)递归填充算法_算法实现_06

填充过程2


填充算法(一)递归填充算法_递归_07

填充过程3


填充算法(一)递归填充算法_递归_08

填充过程4


填充算法(一)递归填充算法_递归_09

填充完毕:填充色为蓝色


(2)边界填充算法(替换边界内所有像素点的颜色):下例根据黑色边框填充

填充算法(一)递归填充算法_搜索_10

填充前同上


填充算法(一)递归填充算法_递归_11

填充完效果

6、下面是一个填充效果截图,另附一个测试工程源码:

填充算法(一)递归填充算法_递归_12


7、MFC下测试代码片段:

(1)初始化界面代码:


void CFillAlgorithm_TestDlg::DrawTestGraphics(CDC* pDC)
{
	CPen pen(PS_SOLID, 4, RGB(0, 0, 0));
	CPen* pOldPen = pDC->SelectObject(&pen);

	CRect rc;
	GetClientRect(&rc);
	pDC->Rectangle(&rc);

	pDC->MoveTo(CPoint(50, 50));
	pDC->LineTo(CPoint(250, 50));
	pDC->LineTo(CPoint(250, 210));
	pDC->LineTo(CPoint(450, 210));
	pDC->LineTo(CPoint(450, 450));
	pDC->LineTo(CPoint(200, 450));
	pDC->LineTo(CPoint(200, 270));
	pDC->LineTo(CPoint(50, 270));
	pDC->LineTo((CPoint(50, 50)));

	pDC->FillRect(&CRect(CPoint(100, 100), CPoint(180, 180)), &CBrush(RGB(255, 255, 0)));
	pDC->FillRect(&CRect(CPoint(202, 212), CPoint(448, 448)), &CBrush(RGB(255, 0, 0)));

	pDC->Ellipse(&CRect(CPoint(360, 50), CPoint(460, 100)));

	CFont font;
	VERIFY(font.CreatePointFont(350, _T("Arial"), pDC));
	CFont* pOldFont = pDC->SelectObject(&font);

	pDC->Rectangle(&CRect(CPoint(500, 120), CPoint(1000, 400)));

	pDC->SetBkMode(TRANSPARENT);
	pDC->SetTextColor(RGB(255, 0, 0));
	pDC->DrawText(_T("请等待填充完成\n弹出提示对话框后\n再操作鼠标,\n以防程序停止工作!"), &CRect(CPoint(500, 120), CPoint(1000, 400)), DT_CENTER | DT_VCENTER);
	

	pen.DeleteObject();
	font.DeleteObject();
	pDC->SelectObject(pOldPen);
}



(2)算法实现及调用:


//
// 4联通递归算法
void CFillAlgorithm_TestDlg::OnFlood()
{
	// TODO: 在此添加命令处理程序代码
	if (m_ptStrat == m_ptOrigin)
	{
		AfxMessageBox(_T("提示:请先单击鼠标左键确定填充的起始点!"));
	}
	else
	{
		CDC* pDC = GetDC();
		COLORREF colorOld = pDC->GetPixel(m_ptStrat);
		DWORD startTm = GetTickCount();
		FloodSeedFill1(pDC, m_ptStrat.x, m_ptStrat.y, colorOld, m_colorFill);
		DWORD finishTm = GetTickCount();
		CString str;
		str.Format(_T("注入式填充完毕,用时:%d ms"), finishTm - startTm);
		AfxMessageBox(str);
		ReleaseDC(pDC);
	}
}
void CFillAlgorithm_TestDlg::FloodSeedFill1(CDC* pDC, int x, int y, COLORREF colorOld, COLORREF colorNew)
{
	if (pDC->GetPixel( CPoint(x, y) ) == colorOld)
	{
		pDC->SetPixel( CPoint(x, y), colorNew);

		FloodSeedFill1(pDC, x, y - 1, colorOld, colorNew); // 上
		FloodSeedFill1(pDC, x + 1, y, colorOld, colorNew); // 右
		FloodSeedFill1(pDC, x, y + 1, colorOld, colorNew); // 下
		FloodSeedFill1(pDC, x - 1, y, colorOld, colorNew); // 左
	}
}

void CFillAlgorithm_TestDlg::OnFlood2()
{
	// TODO: 在此添加命令处理程序代码
	if (m_ptStrat == m_ptOrigin)
	{
		AfxMessageBox(_T("提示:请先单击鼠标左键确定填充的起始点!"));
	}
	else
	{
		CDC* pDC = GetDC();
		COLORREF colorOld = pDC->GetPixel(m_ptStrat);
		DWORD startTm = GetTickCount();
		FloodSeedFill2(pDC, m_ptStrat.x, m_ptStrat.y, colorOld, m_colorFill);
		DWORD finishTm = GetTickCount();
		CString str;
		str.Format(_T("注入式填充2完毕,用时:%d ms"), finishTm - startTm);
		AfxMessageBox(str);
		ReleaseDC(pDC);
	}
}
void CFillAlgorithm_TestDlg::FloodSeedFill2(CDC* pDC, int x, int y, COLORREF colorOld, COLORREF colorNew, int nIgnore /*= 0*/)
{
	if (pDC->GetPixel( CPoint(x, y) ) == colorOld)
	{
		pDC->SetPixel( CPoint(x, y), colorNew);

		switch (nIgnore)
		{
		case 0:
			FloodSeedFill2(pDC, x, y - 1, colorOld, colorNew, 1); // 上
			FloodSeedFill2(pDC, x + 1, y, colorOld, colorNew, 2); // 右
			FloodSeedFill2(pDC, x, y + 1, colorOld, colorNew, 3); // 下
			FloodSeedFill2(pDC, x - 1, y, colorOld, colorNew, 4); // 左
			break;

		case 1:
			FloodSeedFill2(pDC, x, y - 1, colorOld, colorNew, 1); // 上
			FloodSeedFill2(pDC, x + 1, y, colorOld, colorNew, 2); // 右
			//FloodSeedFill2(pDC, x, y + 1, colorOld, colorNew, 3); // 下
			FloodSeedFill2(pDC, x - 1, y, colorOld, colorNew, 4); // 左
			break;

		case 2:
			FloodSeedFill2(pDC, x, y - 1, colorOld, colorNew, 1); // 上
			FloodSeedFill2(pDC, x + 1, y, colorOld, colorNew, 2); // 右
			FloodSeedFill2(pDC, x, y + 1, colorOld, colorNew, 3); // 下
			//FloodSeedFill2(pDC, x - 1, y, colorOld, colorNew, 4); // 左
			break;

		case 3:
			//FloodSeedFill2(pDC, x, y - 1, colorOld, colorNew, 1); // 上
			FloodSeedFill2(pDC, x + 1, y, colorOld, colorNew, 2); // 右
			FloodSeedFill2(pDC, x, y + 1, colorOld, colorNew, 3); // 下
			FloodSeedFill2(pDC, x - 1, y, colorOld, colorNew, 4); // 左
			break;

		case 4:
			FloodSeedFill2(pDC, x, y - 1, colorOld, colorNew, 1); // 上
			//FloodSeedFill2(pDC, x + 1, y, colorOld, colorNew, 2); // 右
			FloodSeedFill2(pDC, x, y + 1, colorOld, colorNew, 3); // 下
			FloodSeedFill2(pDC, x - 1, y, colorOld, colorNew, 4); // 左
			break;

		default:
			break;
		}
	}
}

void CFillAlgorithm_TestDlg::OnBoundary()
{
	// TODO: 在此添加命令处理程序代码
	if (m_ptStrat == m_ptOrigin)
	{
		AfxMessageBox(_T("提示:请先单击鼠标左键确定填充的起始点!"));
	}
	else
	{
		CDC* pDC = GetDC();
		AfxMessageBox(_T("边界填充以黑色为边界"));
		DWORD startTm = GetTickCount();
		BoundarySeedFill1(pDC, m_ptStrat.x, m_ptStrat.y, RGB(0, 0, 0), m_colorFill);
		DWORD finishTm = GetTickCount();
		CString str;
		str.Format(_T("边界式填充完毕,用时:%d ms"), finishTm - startTm);
		AfxMessageBox(str);
		ReleaseDC(pDC);
	}
}
void CFillAlgorithm_TestDlg::BoundarySeedFill1(CDC* pDC, int x, int y, COLORREF colorBoundary, COLORREF colorNew)
{
	COLORREF colorCurrent = pDC->GetPixel( CPoint(x, y) );
	if (colorCurrent != colorBoundary && colorCurrent != colorNew)
	{
		pDC->SetPixel( CPoint(x, y), colorNew);

		BoundarySeedFill1(pDC, x, y - 1, colorBoundary, colorNew);
		BoundarySeedFill1(pDC, x, y + 1, colorBoundary, colorNew);
		BoundarySeedFill1(pDC, x - 1, y, colorBoundary, colorNew);
		BoundarySeedFill1(pDC, x + 1, y, colorBoundary, colorNew);
	}
}

void CFillAlgorithm_TestDlg::OnBoundary2()
{
	// TODO: 在此添加命令处理程序代码
	if (m_ptStrat == m_ptOrigin)
	{
		AfxMessageBox(_T("提示:请先单击鼠标左键确定填充的起始点!"));
	}
	else
	{
		CDC* pDC = GetDC();
		AfxMessageBox(_T("边界填充以黑色为边界"));
		DWORD startTm = GetTickCount();
		BoundarySeedFill2(pDC, m_ptStrat.x, m_ptStrat.y, RGB(0, 0, 0), m_colorFill);
		DWORD finishTm = GetTickCount();
		CString str;
		str.Format(_T("边界式填充2完毕,用时:%d ms"), finishTm - startTm);
		AfxMessageBox(str);
		ReleaseDC(pDC);
	}
}
void CFillAlgorithm_TestDlg::BoundarySeedFill2(CDC* pDC, int x, int y, COLORREF colorBoundary, COLORREF colorNew, int nIgnore /*= 0*/)
{
	COLORREF colorCurrent = pDC->GetPixel( CPoint(x, y) );
	if (colorCurrent != colorBoundary && colorCurrent != colorNew)
	{
		pDC->SetPixel( CPoint(x, y), colorNew);

		switch (nIgnore)
		{
		case 0:
			BoundarySeedFill2(pDC, x, y - 1, colorBoundary, colorNew, 1); // 上
			BoundarySeedFill2(pDC, x, y + 1, colorBoundary, colorNew, 2); // 下
			BoundarySeedFill2(pDC, x - 1, y, colorBoundary, colorNew, 3); // 左
			BoundarySeedFill2(pDC, x + 1, y, colorBoundary, colorNew, 4); // 右
			break;

		case 1:
			BoundarySeedFill2(pDC, x, y - 1, colorBoundary, colorNew, 1); // 上
			//BoundarySeedFill2(pDC, x, y + 1, colorBoundary, colorNew, 2); // 下
			BoundarySeedFill2(pDC, x - 1, y, colorBoundary, colorNew, 3); // 左
			BoundarySeedFill2(pDC, x + 1, y, colorBoundary, colorNew, 4); // 右
			break;

		case 2:
			//BoundarySeedFill2(pDC, x, y - 1, colorBoundary, colorNew, 1); // 上
			BoundarySeedFill2(pDC, x, y + 1, colorBoundary, colorNew, 2); // 下
			BoundarySeedFill2(pDC, x - 1, y, colorBoundary, colorNew, 3); // 左
			BoundarySeedFill2(pDC, x + 1, y, colorBoundary, colorNew, 4); // 右
			break;

		case 3:
			BoundarySeedFill2(pDC, x, y - 1, colorBoundary, colorNew, 1); // 上
			BoundarySeedFill2(pDC, x, y + 1, colorBoundary, colorNew, 2); // 下
			BoundarySeedFill2(pDC, x - 1, y, colorBoundary, colorNew, 3); // 左
			//BoundarySeedFill2(pDC, x + 1, y, colorBoundary, colorNew, 4); // 右
			break;

		case 4:
			BoundarySeedFill2(pDC, x, y - 1, colorBoundary, colorNew, 1); // 上
			BoundarySeedFill2(pDC, x, y + 1, colorBoundary, colorNew, 2); // 下
			//BoundarySeedFill2(pDC, x - 1, y, colorBoundary, colorNew, 3); // 左
			BoundarySeedFill2(pDC, x + 1, y, colorBoundary, colorNew, 4); // 右
			break;

		default:
			break;
		}
	}
}



//
// 8联通递归算法
typedef struct tagDIRECTION
{
	int x_offset;
	int y_offset;
}DIRECTION;
DIRECTION direction_8[] = { {-1, 0}, {-1, 1}, {0, 1}, {1, 1}, {1, 0}, {1, -1}, {0, -1}, {-1, -1} };
//DIRECTION direction_4[] = { {-1, 0}, {0, 1}, {1, 0}, {0, -1} };

void CFillAlgorithm_TestDlg::OnFlood3()
{
	// TODO: 在此添加命令处理程序代码
	if (m_ptStrat == m_ptOrigin)
	{
		AfxMessageBox(_T("提示:请先单击鼠标左键确定填充的起始点!"));
	}
	else
	{
		CDC* pDC = GetDC();
		COLORREF colorOld = pDC->GetPixel(m_ptStrat);
		DWORD startTm = GetTickCount();
		FloodSeedFill3(pDC, m_ptStrat.x, m_ptStrat.y, colorOld, m_colorFill);
		DWORD finishTm = GetTickCount();
		CString str;
		str.Format(_T("注入式填充3(8联通)完毕,用时:%d ms"), finishTm - startTm);
		AfxMessageBox(str);
		ReleaseDC(pDC);
	}
}
void CFillAlgorithm_TestDlg::FloodSeedFill3(CDC* pDC, int x, int y, COLORREF colorOld, COLORREF colorNew)
{
	if (pDC->GetPixel( CPoint(x, y) ) == colorOld)
	{
		pDC->SetPixel( CPoint(x, y), colorNew);

		for(int i = 0; i < 8; i++)
		{
			FloodSeedFill3(pDC, x + direction_8[i].x_offset, y + direction_8[i].y_offset, colorOld, colorNew);
		}
	}
}	

void CFillAlgorithm_TestDlg::OnBoundary3()
{
	// TODO: 在此添加命令处理程序代码
	if (m_ptStrat == m_ptOrigin)
	{
		AfxMessageBox(_T("提示:请先单击鼠标左键确定填充的起始点!"));
	}
	else
	{
		CDC* pDC = GetDC();
		AfxMessageBox(_T("边界填充以黑色为边界"));
		DWORD startTm = GetTickCount();
		BoundarySeedFill3(pDC, m_ptStrat.x, m_ptStrat.y, RGB(0, 0, 0), m_colorFill);
		DWORD finishTm = GetTickCount();
		CString str;
		str.Format(_T("边界式填充3(8联通)完毕,用时:%d ms"), finishTm - startTm);
		AfxMessageBox(str);
		ReleaseDC(pDC);
	}
}
void CFillAlgorithm_TestDlg::BoundarySeedFill3(CDC* pDC, int x, int y, COLORREF colorBoundary, COLORREF colorNew)
{
	COLORREF colorCurrent = pDC->GetPixel( CPoint(x, y) );
	if (colorCurrent != colorBoundary && colorCurrent != colorNew)
	{
		pDC->SetPixel( CPoint(x, y), colorNew);

		for(int i = 0; i < 8; i++)
		{
			BoundarySeedFill3(pDC, x + direction_8[i].x_offset, y + direction_8[i].y_offset, colorBoundary, colorNew);
		}
	}
}




8、源码: