无限位图


10

我想在运行时构建位图。位图应在所有方面都可扩展,像素访问应安静高效。

一些插图http://img546.imageshack.us/img546/4995/maptm.jpg

在图片所示命令之间以及之后,Map.setPixel()和Map.getPixel()应当设置/返回保存在位图中的数据。

我不希望实现只是一个概念,即如何以setPixel()/ getPixel尽可能快的方式分配内存。


灰场是总是点(0,0)还是也可以是其他坐标?
猎鹰

2
需要更多详细信息。设置的像素会稀疏吗?您愿意以多快的速度制作extendX方法以加快setPixelgetPixel的速度?
彼得·泰勒

1
位图会太大而无法容纳在内存中吗?快速操作应该是什么-扩展,setPixel(),getPixel()?

1
@Falcon:不,有足够的时间
SecStone

3
我投票关闭此问题为离题,因为该问题在很大程度上取决于所包含的图像,此图像已被删除。就目前而言,这没有什么意义。

Answers:


9

如果extend()操作需要相当快,那么四叉树可能是一个很好的选择。实际上,它甚至不需要显式的扩展操作。诚然,对于随机访问单个像素而言,它不会产生最佳性能,但是您的评论说,您的主要操作是在像素上进行迭代,四叉树可以非常快地完成,也许几乎与基于矩阵的实现一样快(并且更快)如果迭代并非总是以矩阵布局的相同方式发生)。

您的要求实际上听起来像是您正在尝试实现像生命游戏这样的细胞自动机。您可能想看看Hashlife,这是一种在无限网格上实施“人生游戏”的极高性能方法。请注意,它基于四叉树,但是会根据游戏规则的位置进行一些非常聪明的附加优化。


谢谢你的主意!我将做一些测试并报告结果。
SecStone 2011年

2

@SecStone表示有足够的​​时间用于扩展操作,因此存储像素的最简单,最有效的方法是使用单个平面阵列或二维阵列,因为可以在固定时间内访问像素。


4
如果您对如何应对扩展提出很好的建议,我将对此表示赞成。
布朗

@Doc Brown:如果有足够的时间,只需移动阵列即可。或者,也许您可​​以使用块和转换函数来实现指向数组和块索引的点(也可以在恒定时间内运行)。
猎鹰

2

用手

如果内存不是非常稀疏的资源,我考虑使用更大的块。
这是一些伪代码。

class Chunk {
    Chunk new(int size) {...}
    void setPixel(int x, int y, int value) {...}
    int getPixel(int x, int y) {...}
}

class Grid {
    Map<int, Map<Chunk>> chunks;
    Grid new(int chunkSize) {...}
    void setPixel(int x, int y, int value) {
         getChunk(x,y).setPixel(x % chunkSize, y % chunkSize, value);//actually the modulo could be right in Chunk::setPixel and getPixel for more safety
    }
    int getPixel(int x, int y) { /*along the lines of setPixel*/ }
    private Chunk getChunk(int x, int y) {
         x /= chunkSize;
         y /= chunkSize;
         Map<Chunk> row = chunks.get(y);
         if (row == null) chunks.set(y, row = new Map<Chunk>());
         Chunk ret = row.get(x);
         if (ret == null) row.set(x, ret = new Chunk(chunkSize));
         return ret;
    }
}

这种实现是很幼稚的。
首先,它在getPixel中创建块(基本上,如果没有为该位置定义块,则简单地返回0左右就可以了)。其次,它基于这样的假设,即您具有足够快速和可伸缩的Map实现。据我所知,每种体面的语言都有一种。

另外,您还必须使用块大小。对于密集位图,较大的块大小是好的,对于稀疏位图,较小的块大小更好。实际上,对于非常稀疏的像素来说,“块大小”为1是最好的,这会使“块”本身过时,并将数据结构缩小为像素的int映射的int映射。

现成的

另一个解决方案可能是查看某些图形库。实际上,他们非常擅长将一个2D缓冲区绘制到另一个2D缓冲区中。这意味着您只需分配一个更大的缓冲区,然后在相应的坐标处将原始缓冲区拖入其中。

作为一般策略:当有一个“动态增长的内存块”时,一旦使用完,最好分配它的倍数。这是相当耗费内存的,但是可以大大减少分配和复制成本。大多数矢量实现分配的大小超过其两倍。因此,特别是如果您使用现成的解决方案,请不要将缓冲区仅扩展1个像素,因为只请求了一个像素。分配的内存很便宜。重新分配,复制和释放非常昂贵。


1

几点建议:

  • 如果将其实现为某种整数类型的数组(或...的数组),则可能应每次将后备数组的位数增加一些位数/像素,以避免在复制它们时移位。不利的一面是您使用了更多的空间,但是随着位图变大,浪费的空间比例下降了。

  • 如果使用基于Map的数据结构,则可以通过简单地重新定位getPixeland setPixel调用的x,y坐标参数来解决增长位图的问题。

  • 如果您使用基于地图的数据结构,则只需要“一个”的地图条目。可以通过缺少条目来指示“零”。这样可以节省大量空间,尤其是在位图大部分为零的情况下。

  • 您不需要使用“地图地图”。您可以将intx,y对编码为单个long。可以使用类似的过程将数组的数组映射到数组。


最后,您需要权衡3件事:

  1. 的性能getPixelsetPixel
  2. extend*运营绩效,以及
  3. 空间利用率。

1

在尝试其他更复杂的事情之前,除非不能将所有内容都保存在内存中,否则请保持简单,并使用二维数组以及有关坐标系原点的信息。要扩展它,请使用相同的策略,例如C ++ std :: vector这样做:区分数组的实际大小和数组的容量,并在达到限制时以块为单位扩展容量。这里的“容量”应根据间隔(from_x,to_x),(from_y,to_y)来定义。

这可能需要不时地对内存进行完全重新分配,但是只要这种情况不会经常发生,它就可以达到您的目的足够快的速度(实际上,您必须对此进行尝试/分析)。


1

进行像素访问的绝对最快的方法是分别寻址像素的二维阵列。

对于扩展,请从一个简单的实现开始,该实现每次都会重新分配和复制(因为无论如何您都需要该代码)。如果剖析不表示您花费大量时间进行处理,则无需进一步完善。

如果分析显示有必要减少重新分配的次数,并且您没有受到内存的限制,请考虑在每个方向上按一定百分比过度分配并存储与原点的偏移量。(例如,如果您从1x1开始一个新的位图并分配一个9x9数组来保存它,则初始值xy偏移量将是4。)这里的权衡是在像素访问期间必须进行额外的数学运算才能应用偏移量。

如果扩展真的很昂贵,则可以尝试以下一种或两种方法:

  • 以不同的方式处理垂直和水平扩展。可以通过分配一个新块并将整个现有数组的单个副本复制到新内存中的适当偏移位置来实现在任何方向上垂直扩展数组。将其与水平扩展相比较,在水平扩展中,每行必须执行一次该操作,因为现有数据在新块中不是连续的。

  • 跟踪最频繁的延伸量和延伸方向。使用该信息来选择新的大小和偏移量,这将减少必须为任何扩展名进行重新分配和复制的可能性。

就个人而言,除非像素访问扩展比很低,否则我是否会需要其中之一。


1
  • 恒大小平铺(比方说,256×256,但是瓷砖的无穷数)
  • 提供允许负像素坐标的位图包装器(给人的印象是图像可以在所有四个方向上展开,而不必重新计算/同步对现有坐标值的引用)
    • 但是,实际的位图类(在包装器下工作)应仅支持绝对(非负)坐标。
  • 在包装器下,使用内存映射的I / O提供图块级(块级)访问
  • 除了Map.setPixel()Map.getPixel()一次修改单个像素,还提供了一次复制和修改一个像素矩形的方法。这将使呼叫者可以根据呼叫者可用的信息来选择更有效的访问方式。
    • 商业库还提供一种更新方法:一步来更新像素:一行像素,一列像素,分散/聚集更新以及算术/逻辑阻击器操作(以最大程度地减少数据复制)。

(让我们不支持热闹的答案...)


0

最灵活,也许最可靠的实现是一个链表,该链表的结构给出x坐标,y坐标和位值。我会先构建它并使它工作。

然后,如果速度太慢和/或太大,请尝试加快速度的常用方法:数组,矩阵,位图,压缩,缓存,取反,仅存储“ 1”值等。

与修正错误的快速实现相比,使缓慢的正确实现更快变得容易。在测试“快速”的第二种实现时,您有一个参照标准可以与之进行比较。

而且,谁知道,您可能会发现慢速版本足够快。只要整个结构适合内存,事情就已经非常快了。


3
-1:“使其在快速运行之前工作”并不是从最糟糕的实施开始的充分理由。此外,几乎没有代码不需要完全更改基础数据结构,因此此处的迭代完全是一种明智的建议。
Michael Borgwardt

这就是为什么您有一个API。该API隐藏了基础实现。SetValue(MyMatrix,X,Y,Value)和GetValue(MyMatrix,X,Y)隐藏MyMatrix是1还是2二维数组,链接列表,缓存在磁盘上,SQL表还是其他内容。调用者可能需要重新编译,但无需更改代码。
安迪·坎菲尔德

0

我提出以下Python实现:

class Map(dict): pass

具有以下优点:

  1. 获取/通过设置访问map[(1,2)]可以考虑O(1)
  2. 显式扩展网格的需求消失了。
  3. 错误的空间很小。
  4. 如有必要,它可以轻松升级到3D。

0

如果您实际上需要任意大小的位图-我的意思是从1x1到1000000x1000000 amd,并且需要按需扩展...一种可能的方法是使用数据库。乍一看似乎很不直观,但是您真正拥有的是存储问题。数据库将使您能够访问单个像素并本质上存储任何数量的数据。我不一定是指SQL数据库,顺便说一句。

它会足够快达到您的目的吗?我无法回答,因为这里没有关于您使用此位图的操作的上下文。但是,如果这是用于屏幕显示,请考虑到您通常只需要拉回其他扫描行即可在屏幕滚动时显示,而不是所有数据。

话虽这么说,我不禁想知道您是否要解决某些问题。您是否应该改为使用基于矢量的图形并跟踪内存中的各个实体,然后仅将渲染的位图渲染为屏幕所需的大小?


也许是OSGeo地图服务器。
rwong

0

以下是使其正常运行的步骤:

  1. 使用从一个接口到另一个接口的转发来构建一棵对象树,这些对象一起代表位图
  2. 位图的每个“扩展”都将分配自己的内存,并为其提供另一个接口
  3. 示例实现为:

    template<class T, class P>
    class ExtendBottom {
    public:
       ExtendBottom(T &t, int count) : t(t), count(count),k(t.XSize(), count) { }
       P &At(int x, int y) const { if (y<t.YSize()) return t.At(x,y); else return k.At(x, y-t.YSize()); }
       int XSize() const { return t.XSize(); }
       int YSize() const { return t.YSize()+count; }
    private:
       T &t;
       int count;
       MemoryBitmap k;
    };
    

显然,对于真正的实现,XSize()和YSize()无效,但是您需要MinX(),MaxX(),MinY(),MaxY()或类似的东西来保持索引号的一致性。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.