65.9K
CodeProject 正在变化。 阅读更多。
Home

使用颜色进行命中测试

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.86/5 (3投票s)

2010 年 4 月 24 日

CPOL

9分钟阅读

viewsIcon

26563

downloadIcon

912

提供一个支持使用颜色进行命中测试的颜色命中测试用户控件。

Time Zones Demo

引言

在访问一个包含美国时区彩色地图的网页时,我被要求提供我所在的时区;但不是通过点击地图。而是通过一个包含美国时区名称的组合框来提供。我想,在 WinForms 应用程序中拥有一个可点击的地图可能会很有用。本文描述了由此产生的用户控件;控件的调用方式;以及我在此过程中学到的一些经验。

背景

执行命中测试是一项计算复杂的任务。最简单的形式是,问题是某个点是否在某个多边形内。请注意,我在下面的图表中添加了一个圆形。我们可以构建轴对齐边界矩形来包围多边形。

Hit Testing

这些边界矩形使我们能够快速确定一个点(A 或 D)是否在边界矩形内。但是,一个点在边界矩形内并不能充分说明它在多边形内。点(E 和 H)在边界矩形内,但不在多边形内。需要更复杂的计算。读者可以参考两个“点在多边形内”的参考资料,了解计算样本。

使用多边形还有另一个缺点。仔细查看放大后的时区地图。

Enlarges Time Zones

边界边缘的像素定义了该边界。使用这种方法定义边界需要数百,甚至数千个边缘。当然,我们可以稍微作弊,使用起点和终点坐标对来描述边界线。但即便如此,我们仍然需要大量的边缘来定义多边形。最后,如果曲线被包含在边界中,事情就会变得棘手。

假设我们可以用颜色填充每个多边形。

Color Hit Testing

现在我们只需要确定在感兴趣的点上是否有颜色。如果有颜色,我们就位于多边形内;如果没有,我们就位于多边形外。这种命中测试称为颜色命中测试。当感兴趣的多边形足够静态以进行着色时,它就有了应用。对于时区地图,我们可以按如下方式为区域着色。

Colored Time Zones

如果鼠标点击发生在品红色区域,则时区为东部;发生在亮绿色区域,则时区为中部;以此类推。

颜色命中测试用户控件

Color Hit Components

这个 ColorHitTest 用户控件封装了两个项目:一个它将显示的图像和一个节点列表,每个节点包含与图像中出现的颜色相关的数据。 ColorHitTest 用户控件包含以下方法、属性和事件。

名称 描述
构造函数
ColorHitTest
public ColorHitTest ( )

创建 ColorHitTest 用户控件的实例
方法
Add
public void Add ( Color color,
                  string value )

将颜色/值对添加到已保存的颜色/值对中
Clear
public void Clear ( )

删除所有颜色/值对
Contains
(重载)
public bool Contains ( Color color )

返回一个 bool 值,指示指定的颜色是否在已保存的颜色/值对中

public bool Contains ( string value )

返回一个 bool 值,指示指定的区分大小写的值是否在已保存的颜色/值对中

public bool Contains ( string value )  
                       bool  case_sensitive )

返回一个 bool 值,指示指定的值(可能不区分大小写)是否在已保存的颜色/值对中

Get
(重载)
public bool Get ( Color color )

返回与已保存颜色/值对中指定颜色关联的值

public bool Get ( string  value )

从已保存的颜色/值对中返回与指定区分大小写值关联的颜色

public bool Get ( string value )  
                  bool  case_sensitive )

从已保存的颜色/值对中返回与指定值(可能不区分大小写)关联的颜色

Image
(重载)
public void Image ( Bitmap bitmap,  
                    int    width,  
                    int    height )

用指定的 width height 的位图替换控件的图像

public void Image ( Bitmap  bitmap,  
                    int     width,  
                    int     height,  
                    bool    make_transparent )

用指定的 width height 的位图替换控件的图像,可能使位图的背景透明

移除
(重载)
public bool Remove ( Color color )

删除包含指定颜色的已保存颜色/值对

public bool Remove ( string value )

删除包含指定区分大小写值的已保存颜色/值对

public bool Remove ( string  value )  
                     bool    case_sensitive )

从已保存的颜色/值对中删除包含指定值(可能不区分大小写)的已保存颜色/值对

属性
Count
public int Count

返回已保存颜色/值对的数量

事件
OnColorHit 当用户单击图像中的某个位置时,将 ColorHitEventArgs 返回给其订阅者
public ColorHitEventArgs ( Color   color,  
                           string  value )

委托和事件定义如下

// ******************************* delegate ColorHitHandler

public delegate void ColorHitHandler ( object  sender,
                                       ColorHitEventArgs e );

// ***************************************** event OnColorHit

public event ColorHitHandler OnColorHit;

使用控件

使用 ColorHitTest 用户控件执行命中测试需要五个步骤

  1. 准备一个要进行颜色命中测试的位图图像

    我从左边的 GIF 开始,花了大约十个小时的沮丧后,完成了右边的 BMP。在此过程中我学到了很多,并在下面描述了一些经验教训。

    Initial Time Zones Final Time Zones
  2. 调用 ColorHitTest 构造函数
    private   ColorHitest. ColorHitest   time_zones_CHT; 
    time_zones_CHT =   new   ColorHitest. ColorHitest ( )

    当调用 ColorHitTest 构造函数时,用于存储颜色/值对的数据结构会被初始化。

  3. 初始化颜色/值对,例如
    time_zones_CHT.Add (   Color .Magenta ,   "Eastern"   ); 
    time_zones_CHT.Add (   Color .Lime ,      "Central"   ); 
         : 
    time_zones_CHT.Add (   Color .White ,     "Boundaries"   );

    每个颜色/值对被放入一个 ColorNode 中,然后将 ColorNode 添加到 ColorNodes 列表中。放在列表中的 ColorNodes 数量没有实际限制。

  4. 加载用户控件要显示的图像
    time_zones_CHT.Image (  display_bitmap,  
                            desired_width,  
                            desired_height );

    在演示中,包含美国时区的位图大小为 425 宽 x 267 高。我想显示一个更小的图像。为了执行缩放,我添加了 ScaleBitmap 方法,该方法是从文章 Bitmap Manipulation Class With Support For Format Conversion, Bitmap Retrieval from a URL, Overlays, etc.(来自 CodeProject)中提取的。缩放显示的位图非常重要;否则,MouseUp (用于触发 onColorHit 事件)的位置将相对于原始位图报告。这会导致颜色被误解,并返回不正确的关联值。

  5. 订阅命中事件
    time_zones_CHT.OnColorHit +=  
         new CH.ColorHitHandler ( revise_GUI );

    演示 GUI 会根据图像上的颜色/值对进行更新。因此,调用事件处理程序 revise_GUI revise_GUI 的签名是

    private void   revise_GUI (  object  sender,  
                                 ColorHitEventArgs  e )

    ColorHitEventArgs 包含 MouseUp 事件发生位置的颜色和值。

为像我一样“图形能力差”的程序员准备的经验教训

本节包含我在为控件准备图形时学到的一些经验。这绝不是对程序员绘图的完整讨论。我只希望我有限的经验能帮助我的读者。

  1. 在与各种图形编辑器和输出文件类型进行了大量斗争之后,我发现 Microsoft Paint 和位图最符合我的需求。本项目中出现的时区图形是使用 Microsoft Paint 创建的。图像最终保存为位图(.bmp)。我花费了无果的时间处理 JPEG 图像。我发现每次加载 JPEG 图像时,其边缘都会变得模糊,并且我用来填充区域的单一颜色不再是单一颜色。我现在更深入地理解了“有损”的含义。
    BMP Image JPEG Image
  2. 一旦获得所需效果,请立即释放鼠标左键(意外单击鼠标右键会撤销自上次释放鼠标左键以来完成的所有工作)。
  3. 绘制直线时,几乎总是按住 Shift 键。这会将直线强制以 45° 的倍数角度绘制。
  4. 绘制到形状的内部。这可以避免大多数意外的鼠标漂移。

    Draw Inward

  5. 在像素级别绘图时,始终放大图像,直到可以轻松选择单个像素。在 Paint 中,视图?缩放?自定义…。我使用了 600%
  6. 仅当您能与边界保持五个像素以上的距离时才使用画笔。除非您要更改大区域的颜色,否则根本不要使用画笔。在 Microsoft Paint 中使用画笔的好处是它能预览单击鼠标左键时颜色将如何变化。
  7. 了解如何执行撤销上一个操作(通常是 CTRL-Z)。如果您不小心进入了不想更改的区域,这将非常有用。
  8. 在完成复杂形状的颜色替换后,仍然有几个像素未能更改为我想要的颜色。为了最轻松地修复这些问题,我选择一种对比色,然后用新颜色填充该图形。未覆盖的像素可以轻松识别和更改。将错误的像素更改为新颜色,当一切看起来都不错时,将该区域重新填充为所需的颜色。
  9. 我发现使用直线工具更改像素颜色很有用。
  10. 我想要的背景颜色是白色(RGB 255, 255, 255)。不幸的是,背景中有一些脏白色(RGB 255, 251, 240)的污点。为了消除这些污点,我首先使用拾色器工具选择污点颜色。然后我使用颜色填充工具将整个背景填充为污点颜色(确保拾取那些无法填充的小区域。(因为所有水都是连通的,所以我修改了缅因州东北部的区域,使其背景与其余背景连通。)然后我将颜色更改为黑色(RGB 0, 0, 0),并再次使用颜色填充工具填充整个背景。现在出现了一些不是精确污点颜色的小区域。我用黑色填充了这些区域。最后,我用白色填充了背景。结果就是演示中使用的图形。

    很重要的一点是,用于填充背景的填充颜色不能是与背景相邻的颜色。当我执行背景污点颜色修复时,夏威夷不是黑色的,所以可以使用黑色。如果夏威夷是黑色的,我将选择一种不是黑色的颜色来执行背景污点颜色修复。

  11. 我犯了一个错误,没有正确连接分隔时区的线条。为了使油漆填充操作按预期进行,有必要确保两条线段共享一个像素。

    Joining Segments

  12. 在选择颜色时,请从“已知”颜色中选择。这是一个有限的调色板,但它可以轻松识别颜色名称和 ARGB 值。遵循此规则时,我发现 Microsoft Paint 并不太友好。颜色菜单项显示一个颜色对话框,其中不包含已知颜色,并且当鼠标悬停在颜色上时甚至不显示颜色名称。

    从上面回忆

    time_zones_CHT.Add (   Color .Lime ,   "Central"   );

    指定 Color.Lime 比指定 Color.FromArgb ( 255, 0, 255, 0) 更容易。但是,Microsoft Paint 的颜色对话框有点死板。虽然它显示了一组不错的颜色,但它们不是已知颜色,并且当鼠标悬停在颜色上时没有工具提示来指示颜色名称(如果有)。

    我在 Microsoft Paint 界面上挣扎了一段时间,然后决定构建已知颜色调色板工具。虽然该工具不能将选定的颜色直接拖放到 Microsoft Paint 的颜色对话框中,但它显示了将 ARGB 值传输到 Microsoft Paint 所需的所有信息。当“准备就绪”时,该工具将通过 CodeProject 提供。

参考文献

© . All rights reserved.