2

在自定义控件上,我有一系列应根据给定打开的 LED 对象GraphicsPath(例如,见下图)。目前我正在使用graphicsPath.IsVisible(ledPoint),但是由于我有很多 LED,因此通过所有 LED 的迭代可能非常缓慢,尤其是在路径复杂的情况下(例如示例中路径的倒数)。

你们中的任何人有什么想法可以更聪明地加速迭代吗?如果举个例子太复杂,它可能会将我重定向到适当的资源。请考虑该控件位于 GDI+ 中,因此不能选择重新设计到另一个引擎中。

在此处输入图像描述

编辑


在我的 PC (i7 3.6GHz) 上,当GraphicsPath我只有一个简单的 100x100 像素矩形,然后我在我的控件上计算逆,它的大小约为 500x500 像素(因此,结果GraphicsPath将是一个 500x500 的矩形,带有一个“孔” 100x100),测试 6000 个 LED 大约需要 1.5 秒,这会太大影响用户体验。

在 Matthew Watson 回复之后,我将详细介绍我的示例:

//------ Base path test
GraphicsPath path = new GraphicsPath();
path.AddRectangle(new Rectangle(100, 100, 100, 100));

var sw = System.Diagnostics.Stopwatch.StartNew();
for (int x = 0; x < 500; ++x)
    for (int y = 0; y < 500; ++y)
        path.IsVisible(x, y);
Console.WriteLine(sw.ElapsedMilliseconds);

//------ Inverse path test
GraphicsPath clipRect = new GraphicsPath();
clipRect.AddRectangle(new Rectangle(0, 0, 500, 500));
GraphicsPath inversePath = Utility.CombinePath(path, clipRect, CombineMode.Complement);

sw.Restart();
for (int x = 0; x < 500; ++x)
   for (int y = 0; y < 500; ++y)
       inversePath.IsVisible(x, y);
Console.WriteLine(sw.ElapsedMilliseconds);

在我的 PC 上,第一次测试大约 725 毫秒,第二次测试大约 5000 毫秒。这只是一条相当简单的路径。GraphicsPath是由用户的鼠标移动生成的,用户可以执行多种路径组合(反转、联合、交叉......我为此使用GPC )因此,通过测试 的否定来测试反转GraphicsPath.IsVisible()可能会很棘手。

inversePath返回的 from非常Utility.CombinePath简单,有以下几点(左PathPoints、右PathTypes):

在此处输入图像描述

4

2 回答 2

3

一种优化是仅在有效边界矩形内进行测试:

Rectangle r = Rectangle.Round( path.GetBounds() );
for (int x = r.X; x < r.Width; ++x)
     for (int y = r.Y; y < r.Height; ++y)
         if (path.IsVisible( x, y ))..

另一个技巧展平路径,使其仅由线段组成:

Matrix m = new Matrix();
path.Flatten(m, 2);

选择更大的 flatness也有帮助。我发现用12速度很容易翻倍。

我没有使用您的测试平台,但结果应该会有所帮助:

测试这条路径:

在此处输入图像描述

时间过去了:

25781(无边界)

7929(仅限范围内)

3067(在边界内并被 2 展平)

请注意,第一个只有在您没有从 clientrectangle 开始反转时才有帮助,而第二个只有在路径实际上包含曲线时才有帮助。

更新:

采纳 Hans 的建议,这是迄今为止最有效的“优化”:

Region reg = new Region(path);
for (int x = r.X; x < r.Width; ++x)
    for (int y = r.Y; y < r.Height; ++y)
        reg.IsVisible(x, y);

它把我的时间缩短到 10-20 毫秒(!)

所以这不仅仅是一种“优化”。它避免了最可怕的时间浪费,阅读:基本上根本没有时间进入测试,所有时间都进入了设置测试区域。

来自 Hans Passant 的评论:

GraphicsPath.IsVisible 需要将路径转换为引擎盖下的区域。使用 Region(GraphicsPath) 构造函数预先执行此操作,这样您就不必为每一点支付该成本。

请注意,与其他路径相比,我的路径非常复杂;因此,我的储蓄您从带有矩形孔等的矩形所期望的要多得多。像我这样的用户绘制路径(或来自 OP 的路径)很容易倾向于由数百个段组成,而不仅仅是 4-8..

于 2016-07-12T11:10:45.583 回答
1

我想肯定是有别的事情要花时间,因为我的测试表明我可以在不到一秒的时间内测试 250,000 点:

GraphicsPath path = new GraphicsPath();

path.AddLine(  0,   0, 100,   0);
path.AddLine(100,   0, 100, 100);
path.AddLine(100, 100,   0, 100);
path.AddLine(  0, 100,   0,   0);

var sw = Stopwatch.StartNew();

for (int x = 0; x < 500; ++x)
    for (int y = 0; y < 500; ++y)
        path.IsVisible(x, y);

Console.WriteLine(sw.ElapsedMilliseconds);

上面的代码给出的结果少于 900 毫秒。(我在速度低于 3GHz 的旧处理器上运行。)

您应该能够在 25 毫秒内测试 6000 个点。

这似乎表明时间正在其他地方占用。

(请注意,要测试逆向,您所要做的就是使用!path.IsVisible(x, y)而不是path.IsVisible(x, y)。)


针对已编辑的问题:

正如我所说,要测试逆向,您需要做的就是使用!Path.IsVisible(x,y). 您似乎通过添加一个包含矩形来反转它 - 这有效,但不是必需的并且会减慢速度。

以下代码演示了我的意思 - 请注意Trace.Assert()

GraphicsPath path = new GraphicsPath();
path.AddRectangle(new Rectangle(100, 100, 100, 100));

GraphicsPath inversePath = new GraphicsPath();
inversePath.AddRectangle(new Rectangle(100, 100, 100, 100));
inversePath.AddRectangle(new Rectangle(0, 0, 500, 500));

for (int x = 0; x < 500; ++x)
    for (int y = 0; y < 500; ++y)
        Trace.Assert(inversePath.IsVisible(x, y) == !path.IsVisible(x, y));
于 2016-07-12T09:09:21.413 回答