【问题标题】:Render Large Canvases in UserControl在 UserControl 中渲染大画布
【发布时间】:2012-11-23 21:54:30
【问题描述】:

这几天我一直在尝试实现此功能。我已经广泛搜索了关于我正在尝试做什么的类似问题,但我没有遇到直接帮助我解决问题的问题。

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

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

这种方法的问题是在处理大世界时,位图会非常大。我需要这个应用程序在世界尺寸上具有通用性,而且很明显,每次将大型位图渲染到控件上时都会出现性能问题。

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

我想要完成的是一种在内存使用和性能方面达到中间的方法。本质上是双重缓冲世界,以便在重绘控件时不会闪烁(表单调整大小、焦点和模糊、滚动等)。以 Photoshop 为例——当打开的文档溢出容器视口时,它是如何渲染的?

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

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

protected override void OnPaint(PaintEventArgs e)
{
    WorldSettings settings = worldSettings();

    Rectangle bounds = getRenderBounds(e.ClipRectangle),
        drawLocation = new Rectangle(Point.Empty, settings.TileSize);

    e.Graphics.InterpolationMode = 
        System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
    e.Graphics.SmoothingMode = 
        System.Drawing.Drawing2D.SmoothingMode.None;
    e.Graphics.PixelOffsetMode = 
        System.Drawing.Drawing2D.PixelOffsetMode.None;
    e.Graphics.CompositingQuality = 
        System.Drawing.Drawing2D.CompositingQuality.HighSpeed;

    for (int x = bounds.X; x < bounds.Width; x++)
    {
        for (int y = bounds.Y; y < bounds.Height; y++)
        {
            if (!inWorld(x, y))
                continue;

            Tile tile = getTile(x, y);

            if (tile == null)
                continue;

            drawLocation.X = x * settings.TileSize.Width;
            drawLocation.Y = y * settings.TileSize.Height;

            e.Graphics.DrawImage(img, 
                drawLocation, 
                tileRectangle, 
                GraphicsUnit.Pixel);
        }
    }
}

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

【问题讨论】:

    标签: c# gdi+ doublebuffered


    【解决方案1】:

    诀窍是不要为此使用大位图。您只需要一个覆盖可见区域的位图。然后你画出任何可见的东西。

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

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

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

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

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

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

    【讨论】:

    • 太棒了!我已经搞砸了多久,很可能是隧道视野让我什至没有想到这一点。非常感谢您的明确解释!
    • 没问题,伙计。我只是想补充一点,如果以小增量滚动,您不需要重新绘制整个画布。只需将内容向下/向上等,然后仅重新绘制新暴露的间隙。
    • 是的,我试图用直接绘制的方法来实现它,但是使用这种方法时更有意义。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-12-20
    • 1970-01-01
    • 1970-01-01
    • 2012-08-15
    • 2018-04-19
    • 1970-01-01
    相关资源
    最近更新 更多