如何查找位图中绑定区域的所有矩形?

时间:2011-07-21 21:14:49

标签: c# xna computational-geometry tile-engine

我遇到了问题:我的磁贴引擎需要一个算法。

我有一个二维数组存储我的不可走路的瓷砖。

现在我想实现一个光引擎,但这个引擎需要影子船体。

所以我需要一种能够创建这些影子外壳的算法。

我需要一组矩形来绑定阵列的不可走路部分(具有1 s的单元格) 例如:

http://makerland.de/Zerlegt.png

黑色瓷砖是1 s;我需要找到一组完全包围它们的红色矩形。

2 个答案:

答案 0 :(得分:2)

尝试这样的事情:

  1. 创建包含每个所需点的列表。 (在您的情况下,每个1)的坐标

  2. 对于此列表中的每个点:

    1. 从此点向下循环Y轴,直至遇到不合意为止的点(0
    2. 沿着X轴向右循环,直到您点到X坐标,该坐标在您从步骤1获得的点和结束Y坐标之间的任何Y值处都有0
    3. 将您刚找到的矩形添加到矩形列表
    4. 从原始列表中删除矩形中的每个点。
  3. 这可能不是最快的方法,但它应该有效。

答案 1 :(得分:2)

经过深思熟虑后,我想出了一个更快的解决方案:

  1. 使用RangeStartXStartY属性创建不可变EndY结构。

  2. 维护一个稀疏的当前Range数组,其长度等于图像的高度,以及一个可为空的currentRange变量。此数组将包含从每个Y坐标开始的范围(如果有)

  3. 对于每列,

    1. 清除currentRange
    2. 对于列中的每个单元格:

      1. 如果没有currentRange,或者如果有,但它在此单元格之前结束(如果currentRange.EndY <= y),则将currentRange设置为范围数组中的y'元素。 /> 因此,currentRange将始终引用包含当前单元格的范围,如果当前单元格不在范围内,则null

      2. 如果当前单元格为1

        1. 如果我们在一个范围内,什么也不做 - 范围将继续进入下一栏。

        2. 如果我们不在一个范围内,请循环播放列,直到我们点击0或列的结尾,然后创建一个新范围,从1延伸到我们刚刚发现直到循环结束。
          然后,继续执行下一个if(因为当前单元格现在是0或列的末尾,而我们不在范围内)
          如果在前进时创建新范围时达到现有范围,则可以停止新范围并让现有范围继续低于它(最适合模糊边缘),或关闭现有范围(见下文)并让新范围向下延伸以接替现有范围。

      3. 如果当前单元格为0
        1. 如果我们在一个范围内,请关闭范围:
          1. 返回一个新的矩形,从范围的起点X和Y延伸到当前的Y和范围的结束X.
          2. 清除活动范围数组的范围。
  4. 此算法在计算中为O(x * y),在空间中为O(y)。我相信这是解决问题的最快方法。

    您还可以旋转此算法并扫描行而不是列(以便范围向下而不是向右拉伸),这将在存储中O(x)

    这是一个C#实现:

    class BoxFinder {
        class Range {
            public Range(int startX, int startY, int endY) {
                StartX = startX;
                StartY = startY;
                EndY = endY;
            }
    
            public int StartX { get; private set; }
            public int StartY { get; private set; }
            public int EndY { get; private set; }
        }
        public BoxFinder(int[,] data) {
            Data = data;
            Width = data.GetLength(0);
            Height = data.GetLength(1);
            ranges = new Range[Height];
        }
    
        public int[,] Data { get; private set; }
        public int Width { get; private set; }
        public int Height { get; private set; }
    
        private Range[] ranges;
        public IEnumerable<Rectangle> GetBoxes() {
            for (int x = 0; x < Width; x++) {
                Range currentRange = null;
                for (int y = 0; y < Height; y++) {
                    Func<Range, Rectangle> CloseRange = r => {
                        currentRange = null;
                        ranges[r.StartY] = null;
                        return new Rectangle(
                            r.StartY,
                            r.StartX,
                            x - r.StartX,
                            r.EndY - r.StartY
                        );
                    };
    
                    if (currentRange == null || currentRange.EndY <= y)
                        currentRange = ranges[y];
    
                    if (Data[x, y] == 1) {
                        if (currentRange == null) {
                            int startY = y;
                            for (; y < Height && Data[x, y] == 1; y++) {
                                if (ranges[y] != null)
                                    yield return CloseRange(ranges[y]);
                                //If there are fuzzy edges, break; instead
                            }
                            ranges[startY] = new Range(x, startY, y);
                            if (y == Height)
                                continue;
                            //Otherwise, continue to the next if in case a previous range just ended
                        }
                    }
                    //No else; we can get here after creating a range
                    if(Data[x, y] == 0) {
                        if (currentRange != null)
                            yield return CloseRange(currentRange);
                    }
                }
            }
        }
    }