在绘图的过程中,绘图笔每走一步就与规定的图形进行偏差比较,然后决定下一步的走向,所以算法的核心在于步进之后的偏差比较。
逐点比较法的执行过程可用下面的流程图来表示:
对于一般的绘图机,画笔在平面的走向只有水平和垂直方向,所以规定走向为X方向和Y方向。
为了简化起见,直线的起点都固定在坐标系原点,实际实现时,对于任意直线可做坐标系变换,将坐标原点移到起点
参数:所画直线的斜率,作为计算偏差的原始参数
定义:目标直线的与X轴的夹角为α,画笔当前点与原点连线与X轴的夹角为β
偏差值: Δ=tan(β)−tan(α)
当 Δ<0 时,笔当前的位置在直线的下方,下一步向+Y的方向走一步
当 Δ⩾0 时,笔当前的位置在直线的上方,下一步向+X的方向走一步
为了简化计算提高性能,实际算法实现时,常常使用递推法计算偏差。
定义: A(xA,yA) 是目标直线的终点, P(xP,yP) 是当前画笔的坐标。
所以上面提到的 Δ=(xA⋅yp−xp⋅yA)/xP⋅xA
分母恒正,所以只要考虑分子的符号。
当下一步为+Y 时
当下一步为+X 时
于是我们可以省去每步都进行 Δ 的计算,节省资源。
下面汇总任意象限的直线的走向与计算:
象限 | F≥0 | F<0 | ||
---|---|---|---|---|
走向 | 偏差计算 | 走向 | 偏差计算 | |
第一象限 | +X | Fi+1=Fi−|yA| | +Y | Fi+1=Fi+|yA| |
第三象限 | -X | -Y | ||
第二象限 | +Y | Fi+1=Fi−|xA| | -X | Fi+1=Fi+|xA| |
第四象限 | -Y | +X |
关于逐点比较法的一些看法(不一定正确只是自己的思考):逐点比较法是一种十分容易理解且实际操作也非常简便可行的划线法,适用于任意斜率的直线。但是,缺点也是存在的,实际画出来的线段边缘的锯齿感比较严重,而且平直线段和斜直线段给人感觉粗细不一样,究其原因应该是画笔的走向只有X方向和Y方向。于是会画很多多余的像素,增强了直线的锯齿感。
下面要介绍的是一种能画出较平滑、均匀直线的方法。
顾名思义,数值微分法需要根据目标直线求微分。
流程如下:
首先考虑斜率在 [0,1] 范围内的直线。现在做这样的假设,设目标直线 L(P0,P1) (不妨假设 P0<P1 )经过端点 P0(x0,y0) 和 P1(x1,y1) ,则直线段的斜率 k=y1−y0x1−x0 ,然后从 P0 开始,到 P1 为止按 x 轴方向扫描,步进的步长 Δx=1 ,这样 x 每增加 Δx , y 就增加 k 。
参考下面这张图,从起点开始开始,可以这样描述:对于当前点,下一个点的坐标有两种情况:一是 (x0+1,y0) 二是 (x0+1,y0+1) 。判断下一个点的坐标呢?可以考虑这样一件事,就是计算直线在每个整数横坐标的坐标 P(x,y) (当然纵坐标是一个实数,程序中用浮点数),然后判断与P点最接近的点。我们可以看当前纵坐标的小数部分,如果小于0.5,则是第一种情况;如果大于0.5,则是第二种情况。
(这不就是四舍五入吗?)于是也就能够实现这样的画点方法了,相比于逐点比较法,它可以画出相对平滑、均匀的直线。
对于斜率大于1的直线,就要沿纵坐标的方向来扫描。
思考:
在程序实现的过程中每次都将横坐标代入直线方程求解是很没效率的(实际使用的都是计算斜率,然后作为累加量,但是也有误差累加的弊端),而且画点时使用类型转换效率也比较低(事实上,大学教材上就是这样做的,使用(int)…的强制类型转换)。
我们可以借鉴一下上面介绍的逐点比较法的思想,通过正负的判断来区分,就是接下来要介绍的另一种著名的画线法——中点画线法。
同样,首先考虑斜率在 [0,1] 范围内的直线。在上题的假设的基础上,下一点的选择为 P1(xi+1,yi) 和 P2(xi+1,yi+1) 。考虑两点的中点, Q(xi+1,yi+0.5) 。从图上可以看出若中点在直线之上,则选择 P1 ,若中点在直线之下,则选择 P2 。
条件:
直线 l ,过点 (x0,y0),(x1,y1) ;
直线的方程式为: F(x,y)=ax+by+c=0 ,其中 a=y0−y1 , b=x1−x0 , c=x0y1−x1y0
分析:
想要判断中点 M 是在直线的上方还是下方,引入偏差 d=F(M)=F(xp+1,yp+0.5)=a(xp+1)+b(yp+0.5)+c
这里有一个技巧,如果在计算下一点的偏差d时使用的是将下一点的坐标代入的方法,就会增加计算量。下面我们就要研究下一点的偏差和当前偏差的递推关系。
对于起始点的偏差选 d0=F(x0+1,yp+0.5)=a(x0+1)+b(y0+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);
}
}