C# 改进Winforms中的命中测试;是否有GraphicsPath.IsVisible的替代方案?

C# 改进Winforms中的命中测试;是否有GraphicsPath.IsVisible的替代方案?,c#,winforms,gdi+,hittest,graphicspath,C#,Winforms,Gdi+,Hittest,Graphicspath,在一个自定义控件上,我有一系列LED对象,它们应该根据给定的GraphicsPath打开(例如,请参见下图)。目前我使用的是graphicsPath.IsVisible(ledPoint),但由于我有许多LED,因此通过所有LED的迭代可能非常缓慢,尤其是当路径复杂时(例如,示例中的路径相反) 你们中有谁有更聪明的想法来加速迭代吗?如果它太复杂,无法举例说明,那么可以将我重定向到适当的资源。请考虑控制是在GDI+重新设计到另一个引擎不是一个选项。 编辑 在我的PC(i7 3.6GHz)上,当

在一个自定义控件上,我有一系列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);
/----基本路径测试
GraphicsPath路径=新的GraphicsPath();
AddRectangle(新矩形(100100100100));
var sw=System.Diagnostics.Stopwatch.StartNew();
对于(int x=0;x<500;++x)
对于(整数y=0;y<500;++y)
路径是可见的(x,y);
控制台写入线(软件延迟毫秒);
//------反向路径测试
GraphicsPath clipRect=新的GraphicsPath();
AddRectangle(新矩形(0,0500500));
GraphicsPath inversePath=实用程序.CombinePath(路径、clipRect、CombineMode.complete);
sw.Restart();
对于(int x=0;x<500;++x)
对于(整数y=0;y<500;++y)
反向路径可见(x,y);
控制台写入线(软件延迟毫秒);
在我的电脑上,第一次测试约725毫秒,第二次测试约5000毫秒。这只是一条相当简单的道路。
GraphicsPath
由用户的鼠标移动生成,用户可以执行多个路径组合(反转、并集、相交……我用于此)。因此,通过测试
GraphicsPath.IsVisible()
的否定来测试反转可能很棘手

实用程序返回的
inversePath
。CombinePath
非常简单,它有以下几点(左
路径点
,右
路径类型
):


我想一定还有别的事情需要花时间,因为我的测试表明我可以在不到一秒钟的时间内测试250000个点:

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

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

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 ))..
选择较大的
平面度也有帮助。我发现使用
1
2
时,速度很容易翻倍

我没有使用您的测试床,但结果应该有帮助:

测试此路径:

时光流逝:

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);
Region reg=新区域(路径);
对于(int x=r.x;x
它使我的计时时间缩短到10-20毫秒(!)

因此,这不仅仅是一种“鸦片化”;这是为了避免时间的极大浪费,内容如下:基本上没有时间进入测试,所有的时间都进入了设置测试区域

根据Hans Passant的评论:

GraphicsPath.IsVisible要求将路径转换为区域 在引擎盖下。在区域前面执行此操作(图形SPATH) 所以你不必为每一个点都付出代价


注意与其他路径相比,我的路径非常复杂;因此,我的储蓄要比你从一个带矩形孔的长方形或诸如此类的东西中得到的储蓄要多得多用户绘制的路径像我的路径(或OP中的路径)很容易由数百段组成,而不仅仅是4-8段。

图形的来源是什么?还有:到底是什么问题?真的太慢了吗?你有多少条路?你不能将他们的点击缓存在列表或字典中吗?我做了一些测试,请查看我的编辑。我无法缓存,因为GraphicsPath会不断变化除了Matthews回答:这些不断变化的GraphicsPath是如何创建的???@TaW请再次检查我的编辑是什么
实用程序。CombinePath()
?我编辑它是为了向您展示一个更复杂的路径(如反向路径)所产生的问题。我明白您的观点,但是请考虑一下,我的例子只是一个例子,通过用户的行为,如工会、交叉点、排除……,路径可能会更复杂……所以,最终,谁知道测试<代码>路径> ISVISILUBLE()>代码>比否定<代码>快还是慢?path.IsVisible()
?是什么导致路径花费更多或更少的时间?也许一个想法是测量第一个测试点的速度(同时测试
IsVisible()
和!IsVisible()`),然后在下面的两个点中取最快的一个。您认为呢?请记住,您的GraphicsPath过于简单,因此性能保证相当不确定。然而,这段代码很容易被加速。GraphicsPath.IsVisible要求将路径转换为引擎盖下的区域。先使用区域(GraphicsPath)构造器来实现这一点,这样您就不用为每个点都支付成本。使您的代码在我尝试时快6倍。@Hans的建议使我的代码快1000倍,或者,
Matrix m = new Matrix();
path.Flatten(m, 2);
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);