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

  • 相关阅读:
    C#事件(event)解析
    dll加入到GAC后,如何方便的调试
    『C程序设计』读书笔记系列文章之第四章 逻辑运算和判断选取控制
    C#委托之个人理解
    虚方法(virtual)和抽象方法(abstract)的区别
    『C程序设计』读书笔记系列文章之第二章 数据类型、运算符与表达式
    SOA概览(转)
    今天学的几个有用的句型
    【老孙随笔】PPT高手的启示
    【项目经理之修炼(11)】《初级篇》什么样的项目经理才可能成功??
  • 原文地址:https://www.cnblogs.com/sparkleDai/p/7604884.html
Copyright © 2020-2023  润新知