漫水填充(泛洪填充、油漆桶)的C#实现(解决堆溢出问题)
漫水填充也叫泛洪填充,是画图软件中的油漆桶功能,但在运用中,远不止于此,比如构造一个矩阵数据,需要检测边界,在边界的内外填入不同的数据。油漆桶是对图形的快速填充,将图象以位图数据的形式来看,其实也是一个矩阵数据或者说是二维数组,所以我们如果以数字作为矩阵数据,那么只需检测里面的数据即可。然后将数据再绘制成图像,那就是油漆桶的功能。为了保存数据,我们定义了一个数字矩阵,并在矩阵中实现相应的填充方法,代码如下。
#region DigitMatrix
/// <summary>
/// 数字矩阵
/// </summary>
public class DigitMatrix
{
#region 属性
/// <summary>
/// 行数
/// </summary>
public int RowCount { get; private set; }
/// <summary>
/// 列数
/// </summary>
public int ColumnCount { get; private set; }
public int[,] Data { get; private set; }
#endregion
public DigitMatrix(int rowCount, int columnCount)
{
RowCount = rowCount;
ColumnCount = columnCount;
Data = new int[RowCount, ColumnCount];
}
public override string ToString()
{
StringBuilder sb = new StringBuilder();
for (int r = 0; r < RowCount; r++)
{
string line = "";
for (int c = 0; c < ColumnCount; c++)
{
line += Data[r, c];
}
sb.AppendLine(line);
}
return sb.ToString();
}
#region FloodFill8
/// <summary>
/// 漫水填充(8邻域填充)
/// </summary>
/// <param name="r">行</param>
/// <param name="c">列</param>
/// <param name="newValue"></param>
/// <param name="oldValue"></param>
/// <param name="matrix"></param>
public void FloodFill8(int r, int c, int newValue, int oldValue)
{//递归实现可能造成堆栈溢出错误
if (r >= 0 && r < RowCount && c >= 0 && c < ColumnCount &&
Data[r, c] == oldValue && Data[r, c] != newValue)
{
Data[r, c] = newValue;
FloodFill8(r + 1, c, newValue, oldValue);
FloodFill8(r - 1, c, newValue, oldValue);
FloodFill8(r, c + 1, newValue, oldValue);
FloodFill8(r, c - 1, newValue, oldValue);
FloodFill8(r + 1, c + 1, newValue, oldValue);
FloodFill8(r - 1, c - 1, newValue, oldValue);
FloodFill8(r - 1, c + 1, newValue, oldValue);
FloodFill8(r + 1, c - 1, newValue, oldValue);
}
}
#endregion
#region FloodFill8WithStack
/// <summary>
/// 漫水填充(基于堆的8邻域填充)
/// </summary>
/// <param name="r">行</param>
/// <param name="c">列</param>
/// <param name="newValue"></param>
/// <param name="oldValue"></param>
/// <param name="matrix"></param>
public void FloodFill8WithStack(int r, int c, int newValue, int oldValue)
{
var stackRow = new Stack<int>();
var stackColumn = new Stack<int>();
stackRow.Push(r);
stackColumn.Push(c);
bool CheckNewSeed(int r1, int c1)
{
if (r1 >= 0 && r1 < RowCount && c1 >= 0 && c1 < ColumnCount &&
Data[r1, c1] == oldValue && Data[r1, c1] != newValue)
{
Data[r1, c1] = newValue;
stackRow.Push(r1);
stackColumn.Push(c1);
return true;
}
return false;
}
while (true)
{
if (stackRow.Count <= 0)
{
break;
}
r = stackRow.Pop();
c = stackColumn.Pop();
CheckNewSeed(r, c);
CheckNewSeed(r + 1, c);
CheckNewSeed(r - 1, c);
CheckNewSeed(r, c + 1);
CheckNewSeed(r, c - 1);
CheckNewSeed(r + 1, c + 1);
CheckNewSeed(r - 1, c - 1);
CheckNewSeed(r - 1, c + 1);
CheckNewSeed(r + 1, c - 1);
}
}
#endregion
#region FloodFill4
/// <summary>
/// 漫水填充(4邻域填充)
/// </summary>
/// <param name="r">行</param>
/// <param name="c">列</param>
/// <param name="newValue"></param>
/// <param name="oldValue"></param>
public void FloodFill4(int r, int c, int newValue, int oldValue)
{//递归实现可能造成堆栈溢出错误
if (r >= 0 && r < RowCount && c >= 0 && c < ColumnCount
&& Data[r, c] == oldValue && Data[r, c] != newValue)
{
Data[r, c] = newValue;
FloodFill4(r + 1, c, newValue, oldValue);
FloodFill4(r - 1, c, newValue, oldValue);
FloodFill4(r, c + 1, newValue, oldValue);
FloodFill4(r, c - 1, newValue, oldValue);
}
}
#endregion
#region FloodFill4WithStack
/// <summary>
/// 漫水填充(基于堆的4邻域填充)
/// </summary>
/// <param name="r">行</param>
/// <param name="c">列</param>
/// <param name="newValue"></param>
/// <param name="oldValue"></param>
public void FloodFill4WithStack(int r, int c, int newValue, int oldValue)
{
var stackRow = new Stack<int>();
var stackColumn = new Stack<int>();
stackRow.Push(r);
stackColumn.Push(c);
bool CheckNewSeed(int r1, int c1)
{
if (r1 >= 0 && r1 < RowCount && c1 >= 0 && c1 < ColumnCount &&
Data[r1, c1] == oldValue && Data[r1, c1] != newValue)
{
Data[r1, c1] = newValue;
stackRow.Push(r1);
stackColumn.Push(c1);
return true;
}
return false;
}
while (true)
{
if (stackRow.Count <= 0)
{
break;
}
r = stackRow.Pop();
c = stackColumn.Pop();
CheckNewSeed(r, c);
CheckNewSeed(r + 1, c);
CheckNewSeed(r - 1, c);
CheckNewSeed(r, c + 1);
CheckNewSeed(r, c - 1);
}
}
#endregion
#region FloodFillScanRowColumn
/// <summary>
/// 漫水填充(基于行扫描行列的递归填充)
/// </summary>
/// <param name="r">行</param>
/// <param name="c">列</param>
/// <param name="newValue"></param>
/// <param name="oldValue"></param>
public void FloodFillScanRowColumn(int r, int c, int newValue, int oldValue)
{//递归实现可能造成堆栈溢出错误
if (oldValue == newValue) return;
if (Data[r, c] != oldValue) return;
int c1;
//从当前位置扫描至最右边
c1 = c;
while (c1 < ColumnCount && Data[r, c1] == oldValue)
{
Data[r, c1] = newValue;
c1++;
}
//从当前位置扫描至最左边
c1 = c - 1;
while (c1 >= 0 && Data[r, c1] == oldValue)
{
Data[r, c1] = newValue;
c1--;
}
//向上检测新的扫描行
c1 = c;
while (c1 < ColumnCount && Data[r, c1] == newValue)
{
if (r > 0 && Data[r - 1, c1] == oldValue)
{
FloodFillScanRowColumn(r - 1, c1, newValue, oldValue);
}
c1++;
}
c1 = c - 1;
while (c1 >= 0 && Data[r, c1] == newValue)
{
if (r > 0 && Data[r - 1, c1] == oldValue)
{
FloodFillScanRowColumn(r - 1, c1, newValue, oldValue);
}
c1--;
}
//向下检测新的扫描行
c1 = c;
while (c1 < ColumnCount && Data[r, c1] == newValue)
{
if (r < RowCount - 1 && Data[r + 1, c1] == oldValue)
{
FloodFillScanRowColumn(r + 1, c1, newValue, oldValue);
}
c1++;
}
c1 = c - 1;
while (c1 >= 0 && Data[r, c1] == newValue)
{
if (r < RowCount - 1 && Data[r + 1, c1] == oldValue)
{
FloodFillScanRowColumn(r + 1, c1, newValue, oldValue);
}
c1--;
}
//由于检测到新的行之后会递归检测,所以所有的空间都将被检测到。
}
#endregion
#region FloodFillScanRowColumnWithStack
/// <summary>
/// 漫水填充(基于堆的扫描行列的填充)
/// </summary>
/// <param name="r">行</param>
/// <param name="c">列</param>
/// <param name="newValue"></param>
/// <param name="oldValue"></param>
public void FloodFillScanRowColumnWithStack(int r, int c, int newValue, int oldValue)
{
if (oldValue == newValue)
{
Console.WriteLine("区域已经填充,不需要处理。");
return;
}
var stackRow = new Stack<int>();
var stackColumn = new Stack<int>();
int c1;
bool spanBottom;//检测下方
bool spanTop;//检测上方
stackRow.Push(r);
stackColumn.Push(c);
try
{
while (true)
{
r = (stackRow.Count > 0 ? stackRow.Pop() : -1);
if (r == -1) return;
c = (stackColumn.Count > 0 ? stackColumn.Pop() : -1);
c1 = c;
while (c1 >= 0 && Data[r, c1] == oldValue) c1--; // 跳到需填充的最左位置.
c1++; //向右
spanBottom = spanTop = false;
while (c1 < ColumnCount && Data[r, c1] == oldValue)//向上扫描到需要填充的位置
{
Data[r, c1] = newValue;//填充数据
if (!spanBottom && r > 0 && Data[r - 1, c1] == oldValue)//没有在检测下方,而下方有需要填充的位置,将位置压入到堆栈中,然后跳过该行.
{
stackRow.Push(r - 1);
stackColumn.Push(c1);
spanBottom = true;
}
else if (spanBottom && r > 0 && Data[r - 1, c1] != oldValue)//检测下方,向上扫描到边界,则不再检测底边。
{
spanBottom = false;
}
if (!spanTop && r < RowCount - 1 && Data[r + 1, c1] == oldValue) //没有在检测上方, 而上方有需要填充的位置,将位置压入到堆栈中,然后跳过该行.
{
stackRow.Push(r + 1);
stackColumn.Push(c1);
spanTop = true;
}
else if (spanTop && r < RowCount - 1 && Data[r + 1, c1] != oldValue)//检测上方,向上扫描到边界,则不再检测上方。
{
spanTop = false;
}
c1++;//向右扫描
}
}
}
catch (Exception ex)
{
Console.WriteLine($"{r}行,{c}列;{ex.Message}");
}
}
#endregion
}
#endregion边界数据直接填写到DigitMatrix中,然后调用 matrix.FloodFillScanRowColumnWithStack(0, 0, 2, 0);,其中2是newValue,0是oldValue。
实现后的效果图
红色区为填充部分,黑色为图形的边界,白色部分是图形的内部。
参考文章

浙公网安备 33010602011771号