zoukankan      html  css  js  c++  java
  • 漫水填充(泛洪填充、油漆桶)的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)

  • 相关阅读:
    JeePlus:代码生成器
    JeePlus:API工具
    Java实现 洛谷 P1023 税收与补贴问题
    Java实现 洛谷 P1023 税收与补贴问题
    Java实现 洛谷 P1023 税收与补贴问题
    Java实现 洛谷 P1328 生活大爆炸版石头剪刀布
    Java实现 洛谷 P1328 生活大爆炸版石头剪刀布
    Java实现 洛谷 P1328 生活大爆炸版石头剪刀布
    Java实现 洛谷 P1328 生活大爆炸版石头剪刀布
    Java实现 洛谷 P1328 生活大爆炸版石头剪刀布
  • 原文地址:https://www.cnblogs.com/sparkleDai/p/7604884.html
Copyright © 2011-2022 走看看