漫水填充(泛洪填充、油漆桶)的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。

实现后的效果图


红色区为填充部分,黑色为图形的边界,白色部分是图形的内部。

参考文章

漫水填充算法的一个简单实现(Qt版)

图像处理之泛洪填充算法(Flood Fill Algorithm)

posted @ 2017-06-22 11:12  _学而时习之  阅读(1525)  评论(0编辑  收藏  举报