繁体   English   中英

Winforms:如何加快Invalidate()?

[英]Winforms: How to speed up Invalidate()?

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

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

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

我已经尝试了许多方法来进行优化。 如果我画一条较短的线,CPU和滞后时间会减少。 如果删除Invalidate()并保留所有其他代码,则该应用程序运行很快。 如果我使用一个Region(仅跨越图形)来使边界框无效,则它同样慢。 如果将边界框划分为一系列较小的框,这些框相互背靠背放置,从而减小了无效区域,则看不到可见的性能提升。

因此,我在这里茫然。 我如何加快失效速度?

附带说明,Paint.Net和Mspaint都存在相同的缺点。 但是,Word和PowerPoint似乎能够如上所述地画一条线,而没有滞后并且完全没有CPU负载。 这样就有可能达到预期的效果,问题是如何?

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

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

因此,假设您的画布是640x480。 如果您画一条从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个相等大小的框

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

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

在任何需要执行流畅动画的控件中,我的OnPaint事件通常仅调用PaintMe函数。 这样,我可以随时使用该函数重绘控件。

需要说明的是:用户画的是一条直线 ,还是您的线实际上是一串连接鼠标点的线段? 如果该线是从原点到当前鼠标点的直线,请不要Invalidate(),而应使用XOR笔刷绘制一条不可撤消的线,然后再绘制前一条线,仅当用户完成绘制时才无效。

如果要绘制一堆小线段,只需使最新线段的边界框无效即可。

如何拥有一个“发布更新”到实际画布的不同线程。

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM