10

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

现在,如果我在大显示器(在我的情况下为高清)上最大化运行我的应用程序,我的问题就会出现。如果我尝试从(大)画布的一个角到对角线的另一个角画一条线,它将开始滞后并且 CPU 升高。

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

我已经尝试以多种方式对此进行优化。如果我画一条较短的线,CPU 和延迟就会下降。如果我删除 Invalidate() 并保留所有其他代码,则应用程序很快。如果我使用区域(仅跨越图形)而不是边界框来使无效,那么它同样慢。如果我将边界框拆分为一系列背靠背的较小框,从而减少失效区域,则看不到明显的性能提升。

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

另一方面,Paint.Net 和 Mspaint 都存在同样的缺点。然而,Word 和 PowerPoint 似乎能够像上面描述的那样绘制一条线,没有滞后,也没有 CPU 负载。因此有可能达到预期的结果,问题是如何?

4

4 回答 4

9

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

原因是 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)

一个 640x480 的范围,在对角线绘制的线后面刻有 4 个大小相同的框

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

于 2009-06-07T21:29:40.700 回答
2

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

In any control I do that requires some measure of fluid animation my OnPaint event generally only calls a PaintMe function. That way I can use that function to redraw the control at anytime.

于 2009-06-11T04:56:45.790 回答
1

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

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

于 2009-06-05T19:15:20.797 回答
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;
}

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

于 2009-06-05T20:04:55.937 回答