Winforms:如何加速Invalidate()?

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

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

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

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

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

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

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

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

所以,假设你的canvas是640×480。 如果你画一条从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)

一个640×480的范围,在对角线上绘制一条线后面刻有4个相等大小的盒子

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

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

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

怎么样有一个不同的线程“发布更新”到真正的canvas。

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; } 

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

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

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