1、首先介绍两个概念,就是“4-联通算法”和“8-联通算法”。既然是搜索就涉及到搜索的方向问题,从区域内任意一点出发,如果只是通过上、下、左、右四个方向搜索到达区域内的任意像素,则用这种方法填充的区域就称为四连通域,这种填充方法就称为“4-联通算法”。如果从区域内任意一点出发,通过上、下、左、右、左上、左下、右上和右下全部八个方向到达区域内的任意像素,则这种方法填充的区域就称为八连通域,这种填充方法就称为“8-联通算法”。下面引用几张网络上的图片,辅助说明:
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)注入填充算法(替换相连像素点的颜色):
填充前
填充过程1
填充过程2
填充过程3
填充过程4
填充完毕:填充色为蓝色
(2)边界填充算法(替换边界内所有像素点的颜色):下例根据黑色边框填充
填充前同上
填充完效果
6、下面是一个填充效果截图,另附一个测试工程源码:
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、源码: