`
什么世道
  • 浏览: 224450 次
  • 性别: Icon_minigender_1
  • 来自: 长沙
社区版块
存档分类
最新评论

多边形扫描线填充算法简单剖析(Scan-Line Filling)

阅读更多

   推荐博客:YangKang`s Blog

 

    很久一段时间没有更新自己的博客了,这期间的确很压抑,深深的陷入了一个矢量图填充的项目中。当多件事牵连在一起的时候,真一种捉襟见肘的感觉。不管怎样,也算是失之东隅,收之桑榆吧。

 

一、算法简析:

扫描线填充算法的基本思想是:用水平扫描线从上到下(或从下到上)扫描由多条首尾相连的线段构成的多边形,每根扫描线与多边形的某些边产生一系列交点。将这些交点按照x坐标排序,将排序后的点两两成对,作为线段的两个端点,以所填的颜色画水平直线。多边形被扫描完毕后,颜色填充也就完成了。扫描线填充算法也可以归纳为以下4个步骤:

(1)求交点。即扫描线和多边形的交点。

(2)交点排序。

(3)对排序后的点两两匹配。

(4)更新扫描线,判断是否完成多边形扫描。

 

算法的关键是第一步,求交点 。



如上图,可以发现多边形与扫描线的交点有如下 二个特点:

(1) 每次只有相关的几条边可能与扫描线有交点,不必对所有的边进行求交计算;

(2)相邻的扫描线与同一直线段的交点存在步进关系。

 

 

        第一个特点是显而易见的,为了减少计算量,扫描线算法需要维护一张“活动边Active Edge组成的表,称为“活化边表Active Edge Table(AET)”。例如扫描线4的“活化边表”  p1p2和p3p4两条边组成。

      第二个特点,可以进步证明:假设当前扫描线与多边形的某一条边的交点已经通过直线段求交算法计算出来,得到交点的坐标为(x, y),则下一条扫描线与这条边的交点不需要再求交计算,通过步进关系可以直接得到新交点坐标为(x + △x, y + 1)。△x为y增加1个单位时,x增加的单位。即多边形边界的斜率的倒数。

 

    总而言之,言而总之,“活动边表”是扫描线填充算法的核心,保证了填充时不需太多的求交点运算。为了方便活性边表的建立与更新,还需要建立一个边表NET(New Edge Table)来存放所有的边。

 

    再来讨论边的数据结构:扫描线填充算法只关注交点的x坐标,即交点的横坐标;处理下一条扫描线交点时,根据 △x直接得出;还需要边的最大y坐标y_max作为活边表和扫描结束的判断依据;了便于插入和修改,定义为链表结构。

 

    所以,我们定义边的数据结构如下,AET和NET的基本类型均为边类型Edge。

 

typedef struct tagEdge{
	int y_max;     // 边的最大y坐标
	float x;       // 与扫描线交点x坐标
	float dx;      // 斜率的倒数,Δx
	Edge * pNext;  // 下一条边
} Edge;
 

 

    边表NET是根据每条边的y_max为索引边类型Edge数组。看到这个结构是不是非常像HashMap。没错,这就是一个哈希表。但对其对象操作相当繁琐。

 

上图的多边形建立的NET如下:


 

   活化边表AET根据NET中的所有边Edge类型的y_max进行判断,可能相交的直线添加到AET。如上图形的AET如下: 



 

接下来就是直接扫描处理了,将的得到的点两两连线。 

 

二、代码实现

 

//扫描线填充算法
CPtrArray & CShapeFiller::scanLineFill(CPtrArray &m_tranArrShape){
	CPtrArray *pArr = new CPtrArray();
	
	float y_max = (float)INT_MIN;    // y坐标值的最大值
	float y_min = (float)INT_MAX;    // y坐标值的最小值

	//获取y坐标值最大和最小值
	GetYMaxMin(y_max, y_min);

	//最大最小值取整
	int iy_max = (int)y_max;
	int iy_min = (int)y_min;

	//y_max	取整的误差
	float y_deviation = y_min - (float)iy_min;

/* -------------------------- 定义边表NET及活化边表AET ---------------------- */
	//活化边表AET:元素与当前扫描线相交  基本元素:边Edge. 
	//边表NET: 按边的下端点的Y坐标对非水平的边指针数组
	Edge *pAET=NULL;    // 活化边表的表头指针
	Edge **pNET=NULL;   // 边表的表头指针

	pAET = new Edge();  //初始化活动表头指针,第一个元素不用
	pAET->pNext = NULL;

/* ---------------------- 初始化边表NET --------------------------- */

	//边表NET数组的长度 
	int length = (iy_max-iy_min); 

	// 初始化边表NET,第一个元素不用
	pNET=new Edge*[length + 1];

	//初始化边表NET中的每一个指针元素,赋予内存空间
	for(int i = 0;i <= length;i++)
	{
		pNET[i] = new Edge();
		pNET[i]->pNext = NULL;
	}

	//将所有的边添加到NET边表中
	CreateNET(pNET,iy_min);
   
/* --------------------------- 扫描线填充 ----------------------- */
	// 扫描线填充,从最小y坐标开始扫描,下闭上开
	for(int i=iy_min;i < iy_max;i += 1)
	{
		//活动边表AET的头指针
		Edge *pEdgeFirst=pNET[i-iy_min];

		//初始化活化边表AET
		InitializeAET(pEdgeFirst,pAET);

		//对当前AET活化边表进行X坐标值升序排序
		SortAcendX(pAET);

		//添加间距参数判断
		//若间距为0 ,不填充
		if (lineSpacing ==0)
		{
			for(int i=0;i <=length;i++)
				if(pNET[i])
					delete pNET[i];

			if(pAET) 
				delete pAET;
			if(pNET)
				delete[] pNET; 

			//释放指针申请的内存空间
			m_tranArrShape.RemoveAll();
			return pArr;
		}
		else
		{  
			//间距不为0,填充实现
			if(i%lineSpacing==0)
			{
				
				// 遍历活边表,将坐标点存入CPtrArray对象中
				SavePoints(pAET,i,*pArr,y_deviation,x0,y0);
			}

		}

		// 更新扫描线
		UpdateScanLine(pAET,i);

	}

	// 删除边表
	for(int i=0;i <=length;i++)
		if(pNET[i])
			delete pNET[i];

	if(pAET) 
		delete pAET;
	if(pNET)
		delete[] pNET; 

	//释放指针申请的内存空间
	m_tranArrShape.RemoveAll();

	return *pArr;
}
 

 

    由于该项目有一定的特殊性,故洒家在此就不提供每个函数的详细代码。如需了解,欢迎一起交流。

 

三、问题解析

    然后,在此过程中遇到很多一个比较大的问题。当扫描线经过多边形顶点时,会出现缺填充或者误填充嗯的问题。



 

当时是想着判断交点奇偶性,上下顶点对填充没有影响,但是左右顶点对填充会有较大的影响。

 

左右顶点判断(逆时针为正)

左顶点――P1P2P3y坐标满足条件y1 < y2 < y3

 

右顶点――P1P2P3y坐标满足条件y1 > y2 > y3

 

左右顶点修正:

    对于左顶点的情况,采用的修正方法是修改以左顶点为终点的那条边的区间,将顶点排除在区间之外,也就是删除这条边的终点,这样在计算交点时,就可以少计算一个交点,平衡和交点奇偶个数。结合前文定义的数据结构:AET,只要将该边的y_max修改为y_max – 1就可以了。右顶点不处理,左开右闭的策略。

  

   然而,这样虽然能够解决大多数的顶点缺填问题,但是对于几个左右顶点连续排列在一条水平线上时,会出现误填。

 

改进的算法:

   后来意识到扫描的基本思想的在图形的内部填充,填充图形也在图形内部,改进的算法是直接判断得到的两两匹配的点连成的线段是否在图形内部。从而,这个问题得到了完好的解决。

 

   以上算法的都是水平线填充,其实斜线填充经过坐标的一系列的平移旋转变换,最终也可以用以上的算法实现。 同时还要注意阈值处理,浮点型数据的精度问题。 

 

四、总结

    有时候,我们可能看到一些现象,遇到一些问题,形象化给我们的解决方案是:针对这些现象去解决这些问题,然后先天地给予各种前提,假设种种情景,然后经过这种自以为的这种逻辑判断,最后检验其正确性,殊不知,有些事情是设想之外的,或者说前提有无穷多。前提都不成立,结果又何从可信。正如扫描线相交于多边形顶点,单一顶点处理可能会解决所有问题,但任意的顶点组合怎么办呢? 透过现象看本质,也许就会发现起初的前提只是冰山一角,

九牛一毛而已。透过浮云才能与真理的光芒相拥。

 

 

  • 大小: 7.7 KB
  • 大小: 8.2 KB
  • 大小: 8.2 KB
  • 大小: 1.6 KB
  • 大小: 3.1 KB
  • 大小: 10.3 KB
  • 大小: 3.7 KB
3
0
分享到:
评论
4 楼 什么世道 2014-03-31  
发条源 写道
能留个联系方式吗?QQ什么的,新手求教,想请教你些问题~谢谢啦~
QQ:1009425612
3 楼 发条源 2014-03-25  
能留个联系方式吗?QQ什么的,新手求教,想请教你些问题~谢谢啦~
2 楼 什么世道 2014-01-27  
谢谢,美女过奖了。
GLC 写道
帅哥挺不错啊

1 楼 GLC 2014-01-27  
帅哥挺不错啊

相关推荐

    基于Android的多种填充算法测试--计算机图形学

    "ScanLineTest"这个文件可能是实现扫描线填充算法的测试代码,包含了具体的实现逻辑和可能的性能测试代码。通过阅读和理解这部分代码,我们可以深入学习如何在Android环境中实现和优化填充算法。对于开发者来说,这...

    MATLAB Simulink电动助力转向系统(EPS)模型构建与控制方法解析

    内容概要:本文详细介绍了基于MATLAB/Simulink的电动助力转向系统(EPS)模型的构建及其控制方法。首先,文中阐述了EPS在提升驾驶体验和安全性方面的重要意义。接着,重点讲解了四个关键模型的搭建:整车二自由度模型用于研究车辆转向特性;助力特性曲线模型确定不同驾驶条件下助力电机提供的助力力矩;助力电机模型模拟助力电机的工作过程;齿条模型描述助力电机转矩转化为车轮转向的动作。每个模型都有具体的参数设定和代码示例。此外,文章还解释了模型的输入(如前轮转角、方向盘力矩)和输出(转向助力力矩),并指出控制方法基于各模型间的输入输出关系,利用基本数学公式和逻辑判断实现。 适用人群:汽车工程领域的研究人员、工程师和技术爱好者。 使用场景及目标:适用于希望深入了解EPS工作原理的研究人员,以及需要进行EPS系统设计和优化的工程师。目标是掌握EPS系统的建模方法和控制策略,为实际项目提供理论支持和技术指导。 其他说明:文中提供了丰富的代码片段和详细的模型介绍,有助于读者更好地理解和实践。同时强调了EPS对于提高驾驶安全性和舒适性的重要性。

    实训商业源码-帝国cms7.5 7.2 UTF-8移动端同步插件-酷网站-论文模板.zip

    实训商业源码-帝国cms7.5 7.2 UTF-8移动端同步插件-酷网站-论文模板.zip

    基于Lasso分位数回归的数据预测分析及其广泛应用

    内容概要:本文详细介绍了基于Lasso分位数回归的数据回归预测方法。首先阐述了Lasso分位数回归作为一种结合Lasso回归与分位数回归的统计方法,能够在处理变量选择和模型复杂度方面发挥重要作用。接着解释了其基本原理,即在分位数回归基础上加入Lasso正则化项,从而确保模型既能良好拟合数据,又能有效避免过拟合现象。随后讨论了具体实施流程,从数据预处理到最终预测,涵盖了特征选择、模型构建以及参数优化等多个环节。最后强调了该方法在多个行业(如金融、医疗)的实际应用场景及其潜在价值。 适合人群:对统计学、机器学习有一定了解的研究人员和技术爱好者。 使用场景及目标:适用于需要精确预测并同时考虑多维度因素影响的场合,特别是在面对高维数据时,希望通过减少冗余变量来提高预测准确性的情况。 其他说明:文中提到的方法不仅限于特定领域,而是可以在多种不同类型的预测任务中发挥作用,为决策提供科学依据。

    【MATLAB例程】线性卡尔曼滤波的程序,三维状态量和观测量,较为简单,可用于理解多维KF

    这段代码实现了一个 三维状态的扩展卡尔曼滤波 (Extended Kalman Filter, EKF) 算法。通过生成过程噪声和观测噪声,对真实状态进行滤波估计,同时对比了滤波前后状态量的误差和误差累积分布曲线。 只有一个m文件,下载后使用MATLAB打开运行即可,带误差输出。

    毕业设计-百川多公众号集字福袋 2.0.5开源-整站商业源码.zip

    毕业设计-百川多公众号集字福袋 2.0.5开源-整站商业源码.zip

    实训商业源码-多商家营销活动平台V1.3.9小程序前后端完整全开源解密源码-论文模板.zip

    实训商业源码-多商家营销活动平台V1.3.9小程序前后端完整全开源解密源码-论文模板.zip

    ISC大作业论文-CSAPP-2025春

    ISC大作业论文

    毕业论文-在线进销存-整站商业源码.zip

    毕业论文-在线进销存-整站商业源码.zip

    毕业设计-步数宝步数换购小程序 7.8.1-整站商业源码.zip

    毕业设计-步数宝步数换购小程序 7.8.1-整站商业源码.zip

    实训商业源码-叮咚-门店会员卡小程序4.8.2开源-论文模板.zip

    实训商业源码-叮咚-门店会员卡小程序4.8.2开源-论文模板.zip

    毕业论文-芸众圈子社区V1.7.6 开源版-整站商业源码.zip

    毕业论文-芸众圈子社区V1.7.6 开源版-整站商业源码.zip

    配电网有功电压控制的多智能体强化学习实践:Dec-POMDP框架下的七种MARL算法及开源环境构建

    内容概要:本文探讨了多智能体强化学习(MARL)在配电网有功电压控制中的应用。文中介绍了将电压约束转化为势垒函数的方法,并在Dec-POMDP框架下对七种最先进的MARL算法进行了大规模实验。实验表明,设计合理的电压势垒函数对于提高电压控制效果至关重要。此外,作者还建立了开源环境,旨在促进电力社区和MARL社区的合作,推动MARL算法的实际应用。 适合人群:从事电力系统自动化、智能电网研究的专业人士,以及对多智能体系统和强化学习感兴趣的科研人员。 使用场景及目标:适用于需要优化配电网电压控制的场景,特别是希望通过软件手段而非硬件升级来提升电力质量和缓解电力拥塞的情况。目标是展示MARL在电力系统中的潜力,并为后续研究提供工具和支持。 其他说明:文章不仅讨论了理论和技术细节,还包括大量代码片段,帮助读者理解和实践MARL在电压控制中的具体应用。

    PFC3D岩石注浆破坏模拟:注浆速度、流量调节及孔位选择研究

    内容概要:本文基于PFC3D(Particle Flow Code 3D)软件,详细探讨了岩石注浆过程中的破坏现象及其背后的机理。首先介绍了注浆破坏的复杂性,指出这是由材料特性、地质构造和计算机模拟技术共同决定的。接着重点讲解了注浆速度和流量的调整方法,强调适当的速度和流量对于确保注浆效率和避免过度破坏的重要性。最后讨论了在不考虑渗流场的情况下,如何根据岩石结构特征选择最佳的注浆孔位置,以提高注浆效果并保护周围岩石结构。 适合人群:从事地质工程领域的研究人员和技术人员,尤其是那些希望深入了解岩石注浆过程的人。 使用场景及目标:适用于需要利用PFC3D进行岩石注浆模拟的研究项目,旨在帮助用户掌握注浆速度、流量调节技巧以及合理的注浆孔位选择方法。 其他说明:文中提供了简单的PFC3D模拟代码框架,便于读者快速上手实践。同时提醒读者注意实际操作时应结合实验室理论模型和现场具体情况来进行参数优化。

    电力系统研究中的IEEE标准节点仿真模型及其应用

    内容概要:本文详细介绍了IEEE标准节点仿真模型系列,涵盖了从简单到复杂的多个节点配置,如2机5节点、6节点、3机9节点、13节点、5机14节点、15节点、30节点、33节点、34节点、10机39节点以及69节点。所有模型均已成功调试并实现了潮流计算,适用于短路仿真、稳定性研究和电能质量研究等领域。文中还特别强调了三相等效电源的应用,这是模拟真实电力系统的关键要素之一。 适合人群:从事电力系统研究、仿真和优化的专业人士和技术人员。 使用场景及目标:①用于电力系统短路仿真的建模与分析;②评估电力系统的稳定性和可靠性;③研究电能质量问题,提升电力设备的运行效率和寿命。 阅读建议:本文提供了丰富的背景知识和具体应用场景,建议读者结合实际项目需求选择合适的模型进行深入研究和应用。

    实训商业源码-【超人】积分商城 5.2.26-论文模板.zip

    实训商业源码-【超人】积分商城 5.2.26-论文模板.zip

    实训商业源码-思创兼职小程序V6.7.6 开源版-论文模板.zip

    实训商业源码-思创兼职小程序V6.7.6 开源版-论文模板.zip

    2025年手绘风格毕业设计答辩模板范文.pptx

    2025年手绘风格毕业设计答辩模板范文

    【C语言编程】常用算法与数据结构实现:链表、栈、队列、二叉树、排序查找及图结构的实战指南

    内容概要:本文档详细介绍了使用C语言实现常用的数据结构和算法。首先阐述了算法与数据结构的重要性,并具体讲解了链表、栈、队列、二叉树、图等数据结构的实现方法及其操作函数。接着深入探讨了快速排序和二分查找这两种高效的排序与查找算法,提供了完整的代码示例并解释了每个部分的作用。最后还讨论了图结构的深度优先搜索(DFS)和广度优先搜索(BFS)遍历算法,强调了内存管理和防御性编程的重要性。所有代码示例均可直接编译运行,建议在Linux环境下使用gcc编译测试。 适合人群:具备一定编程基础,尤其是熟悉C语言的初学者或有一定经验的研发人员。 使用场景及目标:①帮助读者理解并掌握常见的数据结构(如链表、栈、队列、二叉树、图)及其基本操作;②通过实际编码练习提高读者对经典算法(如快速排序、二分查找)的理解;③培养良好的编程习惯,如内存管理和防御性编程。 阅读建议:由于文档包含大量代码片段和详细的实现步骤,读者应边阅读边动手实践,尝试编译和运行提供的代码示例,同时注意理解每段代码背后的逻辑和设计思想。此外,建议读者关注文档中提到的编程规范和最佳实践,以提升自身的编程技能。

    毕业论文-源导航V1.0-整站商业源码.zip

    毕业论文-源导航V1.0-整站商业源码.zip

Global site tag (gtag.js) - Google Analytics