您的位置:首页 > 其它

常用直线图形生成算法(一)

2016-04-11 20:02 477 查看

常用直线图形的生成算法

1、逐点比较法

在绘图的过程中,绘图笔每走一步就与规定的图形进行偏差比较,然后决定下一步的走向,所以算法的核心在于步进之后的偏差比较

逐点比较法的执行过程可用下面的流程图来表示:

Created with Raphaël 2.1.0开始偏差判别笔画走步已达终点?结束偏差计算yesno

走向规定

对于一般的绘图机,画笔在平面的走向只有水平和垂直方向,所以规定走向为X方向和Y方向。

偏差计算(以0~90°的直线为例)

为了简化起见,直线的起点都固定在坐标系原点,实际实现时,对于任意直线可做坐标系变换,将坐标原点移到起点

参数:所画直线的斜率,作为计算偏差的原始参数

定义:目标直线的与X轴的夹角为α,画笔当前点与原点连线与X轴的夹角为β

偏差值:Δ=tan(β)−tan(α)\Delta=\tan(\beta)-\tan(\alpha)

当Δ<0\Delta<0 时,笔当前的位置在直线的下方,下一步向+Y的方向走一步

当Δ⩾0\Delta\geqslant0 时,笔当前的位置在直线的上方,下一步向+X的方向走一步

用递推法简化偏差的计算

为了简化计算提高性能,实际算法实现时,常常使用递推法计算偏差。

定义:A(xA,yA)A(x_A,y_A)是目标直线的终点,P(xP,yP)P(x_P,y_P)是当前画笔的坐标。

所以上面提到的Δ=(xA⋅yp−xp⋅yA)/xP⋅xA\Delta=(x_A\cdot y_p-x_p \cdot y_A)/x_P\cdot x_A

分母恒正,所以只要考虑分子的符号。

当下一步为+Y 时x2=x1,y2=y1+1x_2=x_1, y_2=y_1+1 代入之后,与之前的偏差进行比较,增量为xAx_A

当下一步为+X 时x2=x1+1,y2=y1x_2=x_1+1, y_2=y_1 代入之后,与之前的偏差进行比较,增量为−yA-y_A

于是我们可以省去每步都进行Δ\Delta的计算,节省资源。

小 结

下面汇总任意象限的直线的走向与计算:

直线图形逐点比较法偏差计算表

象限F≥0F\geq0F<0F<0
走向偏差计算走向偏差计算
第一象限+XFi+1=Fi−|yA|F_{i+1}=F_i-\left| y_A \right|+YFi+1=Fi+|yA|F_{i+1}=F_i+\left| y_A \right|
第三象限-X-Y
第二象限+YFi+1=Fi−|xA|F_{i+1}=F_i-\left| x_A \right|-XFi+1=Fi+|xA|F_{i+1}=F_i+\left| x_A \right|
第四象限-Y+X
关于逐点比较法的一些看法(不一定正确只是自己的思考):逐点比较法是一种十分容易理解且实际操作也非常简便可行的划线法,适用于任意斜率的直线。但是,缺点也是存在的,实际画出来的线段边缘的锯齿感比较严重,而且平直线段和斜直线段给人感觉粗细不一样,究其原因应该是画笔的走向只有X方向和Y方向。于是会画很多多余的像素,增强了直线的锯齿感。

下面要介绍的是一种能画出较平滑、均匀直线的方法。

2、数值微分法(DDA)

顾名思义,数值微分法需要根据目标直线求微分。

流程如下:

Created with Raphaël 2.1.0开始偏差计算坐标计算已达终点?结束笔画走步yesno

首先考虑斜率在[0,1][0,1]范围内的直线。现在做这样的假设,设目标直线L(P0,P1)L(P_0,P_1)(不妨假设P0<P1P_0)经过端点P0(x0,y0)P_0(x_0,y_0)和P1(x1,y1)P1(x_1,y_1),则直线段的斜率k=y1−y0x1−x0k=\frac{y_1-y_0}{x_1-x_0},然后从P0P_0开始,到P1P_1为止按xx轴方向扫描,步进的步长Δx=1\Delta x=1,这样xx每增加Δx\Delta x,yy就增加kk 。

参考下面这张图,从起点开始开始,可以这样描述:对于当前点,下一个点的坐标有两种情况:一是(x0+1,y0)(x_0+1,y_0)二是(x0+1,y0+1)(x_0+1,y_0+1)。判断下一个点的坐标呢?可以考虑这样一件事,就是计算直线在每个整数横坐标的坐标P(x,y)P(x, y)(当然纵坐标是一个实数,程序中用浮点数),然后判断与P点最接近的点。我们可以看当前纵坐标的小数部分,如果小于0.5,则是第一种情况;如果大于0.5,则是第二种情况。

(这不就是四舍五入吗?)于是也就能够实现这样的画点方法了,相比于逐点比较法,它可以画出相对平滑、均匀的直线。

对于斜率大于1的直线,就要沿纵坐标的方向来扫描。

小结

思考:

在程序实现的过程中每次都将横坐标代入直线方程求解是很没效率的(实际使用的都是计算斜率,然后作为累加量,但是也有误差累加的弊端),而且画点时使用类型转换效率也比较低(事实上,大学教材上就是这样做的,使用(int)…的强制类型转换)。

我们可以借鉴一下上面介绍的逐点比较法的思想,通过正负的判断来区分,就是接下来要介绍的另一种著名的画线法——中点画线法。

3、中点画线法



基本思想

同样,首先考虑斜率在[0,1][0,1]范围内的直线。在上题的假设的基础上,下一点的选择为P1(xi+1,yi)P_1(x_i+1,y_i) 和 P2(xi+1,yi+1)P_2(x_i+1,y_i+1)。考虑两点的中点,Q(xi+1,yi+0.5)Q(x_i+1,y_i+0.5)。从图上可以看出若中点在直线之上,则选择P1P_1,若中点在直线之下,则选择P2P_2。

实现方法

条件:

直线 ll,过点(x0,y0),(x1,y1)(x_0,y_0), (x_1,y_1);

直线的方程式为:F(x,y)=ax+by+c=0F(x,y)=ax+by+c=0,其中a=y0−y1a=y_0-y_1,b=x1−x0b=x_1-x_0,c=x0y1−x1y0c=x_0y_1-x_1y_0

分析:

想要判断中点 MM 是在直线的上方还是下方,引入偏差 d=F(M)=F(xp+1,yp+0.5)=a(xp+1)+b(yp+0.5)+cd=F(M)=F(x_p+1,y_p+0.5)=a(x_p+1)+b(y_p+0.5)+c

当 d<0d<0 时,MM在直线的下方,取P2P_2为下一个像素

当 d>0d>0 时,MM在直线的上方,取P1P_1为下一个像素

当 d=0d=0 时,MM在直线上,取P2P_2或P2P_2都可以,约定取P1P_1为下一个像素

这里有一个技巧,如果在计算下一点的偏差d时使用的是将下一点的坐标代入的方法,就会增加计算量。下面我们就要研究下一点的偏差当前偏差递推关系

当选择 P1P_1 为下一点时,d1=F(xp+2,yp+0.5)=a(xp+2)+b(yp+0.5)+c=d+ad_1=F(x_p+2,y_p+0.5)=a(x_p+2)+b(y_p+0.5)+c=d+a;

当选择 P2P_2 为下一点时,d2=F(xp+2,yp+1.5)=a(xp+2)+b(yp+1.5)+c=d+a+bd_2=F(x_p+2,y_p+1.5)=a(x_p+2)+b(y_p+1.5)+c=d+a+b;

对于起始点的偏差选d0=F(x0+1,yp+0.5)=a(x0+1)+b(y0+0.5)+c=a+0.5bd_0=F(x_0+1,y_p+0.5)=a(x_0+1)+b(y_0+0.5)+c=a+0.5b;

这里已经达到了我们的目的,这样只要计算初始偏差,然后就可以随下一个点的选择来计算下一个偏差。这里还有一个可以优化的地方,计算中同时出现了整数和小数,这在程序实现的时候就要类型转换,也会占用更多的内存空间。由于我们只关心偏差的正负状况,改进的方法就是将偏差乘以2,这样就避免了使用浮点数。

下面是一个程序例子:

void MidPointLine (int x0, int y0, int x1, int y1)
{
int a, b, d1, d2, d, x, y;
a = y0 - y1;
b = x1 - x0;
d = 2 * a -b;
d1 = 2 * a;
d2 = 2 * (a + b);
x = x0;y = y0;
drawpixel(x, y);
while(x < x1)
{
if(d < 0)
{
x++; y++; d += d2;
}
else
{
x++; d += d1;
}
drawpixel(x, y);
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: