Winforms:如何加速Invalidate()?

时间:2009-06-05 19:07:08

标签: c# .net winforms gdi+ gdi

我正在GDI +中开发一个保留模式绘图应用程序。应用程序可以将简单的形状绘制到画布并执行基本编辑。执行此操作的数学运算优化到最后一个字节,不是问题。我正在使用内置的Controlstyles.DoubleBuffer。

绘制一个面板

现在,如果我在大显示器上运行我的应用程序(在我的情况下是HD),我的问题就出现了。如果我尝试从(大)画布的一角绘制一条线到对角线的另一角,它将开始滞后并且CPU变高。

我的应用中的每个图形对象都有一个边界框。因此,当我使从最大化应用程序的一个角落到对面角落的行的边界框无效时,该边界框几乎与画布一样大。当用户绘制一条线时,这个边界框的失效因此发生在mousemove事件上,并且有明显的滞后可见。如果线是画布上的唯一对象,则也存在此延迟。

我试图在很多方面对此进行优化。如果我画一条较短的线,CPU和滞后就会下降。如果我删除Invalidate()并保留所有其他代码,该应用程序很快。如果我使用Region(仅跨越图形)来使invalidate而不是boundingbox失效,那就慢了。如果我将边界框拆分成一系列背靠背的小方框,从而减少了无效区域,则无法看到可见的性能增益。

因此我在这里不知所措。如何加快失效?

另外,Paint.Net和Mspaint都有同样的缺点。然而,Word和PowerPoint似乎能够如上所述绘制一条线,没有延迟,也没有CPU负载。因此,有可能达到预期的效果,问题是如何?

4 个答案:

答案 0 :(得分:8)

对于像线条这样的基本显示项目,如果绝对必须使每个绘图周期的整个边界无效,则应考虑将它们分成几个部分。

原因是GDI +(以及GDI本身)使矩形形状的区域无效,就像您使用边界框指定的那样。您可以通过测试一些水平和垂直线与斜率与显示区域方面类似的线来自行验证。

所以,假设你的画布是640x480。如果你画一条从0,0到639,479的线; Invalidate()将使整个区域从顶部的0,0到639,0无效,到底部的0,479到639,479。例如,从0,100到639,100的水平线导致矩形仅高1像素。

区域将具有完全相同的问题,因为区域被视为组合在一起的水平范围集。因此,对于从一个角落到另一个角落的大对角线,为了匹配您设置的边界框,区域必须指定每条垂直线上的每组像素或整个边界框。

所以作为一个解决方案,如果你有一个非常大的线,将它分成四分之一或八分之一,性能应该大大增加。修改上面的例子,如果你只将两部分分成两半 - 你将把无效区域总数减少到0,0 x 319,239加上320,240 x 639,479。

以下是四分之一分裂的视觉示例。粉红色区域是无效的。不幸的是,SO不会让我发布图片或超过1个链接,但这应该足以解释一切。

(四分之一的线分割,总无效区域是表面的1/4)

a 640x480 extent with 4 equal sized boxes carved behind a line drawn across the diagonal

或者,您可能需要考虑重写更新,而不是指定边界框,以便只绘制与必须更新的区域匹配的项目部分。这实际上取决于需要参与绘制更新的对象数量。如果给定帧中有数千个对象,您可以考虑忽略所有无效区域,只重绘整个场景。

答案 1 :(得分:1)

澄清一下:用户是否正在绘制行,或者您的行实际上是一串连接鼠标点的线段?如果该行是从原点到当前鼠标点的直线,请不要使用Invalidate(),而是使用XOR画笔绘制可撤消的行,然后取消绘制上一行,仅在用户完成绘制时无效。

如果你正在绘制一堆小线段,只需使最近一段的边界框无效。

答案 2 :(得分:1)

如何使用不同的帖子“发布更新”到真实画布。

Image paintImage;
private readonly object paintObject = new object();
public _ctor() { paintImage = new Image(); }

override OnPaint(PaintEventArgs pea) {
    if (needUpdate) {
        new Thread(updateImage).Start();
    }
    lock(paintObject) {
        pea.DrawImage(image, 0, 0, Width, Height);
    }
}

public void updateImage() {
    // don't draw to paintImage directly (it might cause threading issues)
    Image img = new Image();
    using (Graphics g = img.GetGraphics()) {
        foreach (PaintableObject po in renderObjects) {
            g.DrawObject(po);
        }
    }
    lock(paintObject){
        using (Graphics g = paintImage.GetGraphics()) {
            g.DrawImage(img, 0, 0, g.Width, g.Height);
        }
    }
    needUpdate = false;
}

只是一个想法,所以我没有测试过代码; - )

答案 3 :(得分:1)

你无法真正加速Invalidate。它之所以慢是因为它将WM_PAINT事件发布到消息队列中。然后被过滤掉,最终你的OnPaint被调用。您需要做的是在MouseMove事件期间直接在控件中绘制。

在任何控制中,我都需要一些流体动画测量,我的OnPaint事件通常只调用PaintMe函数。这样我就可以随时使用该功能重绘控件。