在UserControl中渲染大画布

时间:2012-12-05 22:51:10

标签: c# gdi+ doublebuffered

我一直在努力实施这项工作几天。关于我正在尝试做的事情,我已经广泛搜索了类似的问题,但我没有遇到直接帮助我解决问题的问题。

基本上我将瓷砖渲染到UserControl类的网格上。这是我正在开发的基于Tile Engine的世界编辑器。这是一个开放世界文档的屏幕截图,并刷了一些瓷砖。

enter image description here

最初,我打算在我的控件中使用Bitmap作为世界预览画布。例如,使用画笔工具,当您移动鼠标并向左按下按钮时,它会将光标下方最近的图块设置为画笔的图块,并将其绘制在layer位图上。控件的OnPaint方法被覆盖到相对于paint事件的剪切矩形绘制layer位图的位置。

此方法的问题在于,在处理大型世界时,位图将非常大。我需要这个应用程序具有世界大小的通用性,并且很明显,每当它被无效时将大位图呈现到控件上时会出现性能问题。

目前,我正在控件的覆盖OnPaint事件中直接将控件绘制到控件上。这很棒,因为它不需要大量内存。例如,每个图块(1000, 1000)的{​​{1}}世界(总画布大小为(20, 20))在整个应用程序的内存大约为18mb。虽然不是内存密集型,但它的处理器密集程度很高,因为每次控件无效时,它都会迭代视口中的每个tile。这会产生非常烦人的闪烁。

我想要实现的是一种在内存中使用和性能相遇的方法。本质上是双重缓冲世界,以便在重绘控件时没有闪烁(形式调整大小,聚焦和模糊,滚动等)。以Photoshop为例 - 当它打开容器视口时如何呈现打开的文档?

作为参考,这是我的控件的(20000, 20000)覆盖,它使用上面提到的直接绘制方法。

OnPaint返回一个相对于getRenderBounds的矩形,用于渲染可见的图块,而不是遍历世界中的所有图块并检查它是否可见。

PaintEventArgs.ClipRectangle

如果您需要我的代码中的更多上下文,请发表评论。

1 个答案:

答案 0 :(得分:1)

诀窍是根本不使用大位图。您只需要一个覆盖可见区域的位图。然后你绘制任何可见的东西。

要实现此目的,您需要将数据与位图分开维护。这可以是一个简单的数组或一个数组/列表,其中包含一个简单的类,用于保存每个块的信息,例如世界位置。

当您的区块在可见区域内时,您可以绘制它。您可能需要或不必遍历整个数组,但这不是一个真正的问题(您还可以在单​​独的线程上计算可见数组)。您还可以通过创建区域索引使该功能更加智能,这样您就不会迭代所有块。

要向数组添加新块,请将其画布位置计算为世界坐标,添加它然后再次渲染数组(或绘制块的区域)。

这也是系统绘制带可滚动区域的控件的方式。

启用双缓冲将使其保持清晰且无闪烁。

在这种情况下,我还会使用一个带有单独滚动条的面板,并计算滚动条的相对位置。