在数学上,直线有无穷个点,蛋仔计算机屏幕上显示直线时,需要进行光栅化处理,用离散的像素点逼近这条直线,需要知道这些点的x,y坐标
1.数值微分法DDA
数值微分法根据直线的微分方程来绘制直线
已知直线段两个端点P0(x0,y0),P1(x1,y1)。
则可求得直线的斜率:k = (y2 - y1) / (x2 - x1)
在k,b均求出的条件下,只要知道一个x值,我们就能计算出一个y值。y = kx + b
如果横坐标x每次增加1(我们称其为步进为1,即x = x +1),那么y的步进就为k+b。
x = x + 1
y = y + (k + b)
同样知道一个y值也能计算出x值,此时y的步进为1,x的步进为(1-b)/k。
y = y + 1
x = x +(1 - b) / k
据计算出的x值和y值,向下取整,得到坐标(x’,y’),并在(x’,y’)处绘制直线段上的一点。
为进一步简化计算,通常可令b取0,将起点看作(0,0)。
设当前点的坐标为(xi ,yi),下一个像素点的坐标为(xi+1,yi+1)
则用DDA算法求解(xi+1,yi+1)的计算公式可以概括为:
xi+1 = xi + xStep (1)
yi+1 = yi + yStep (2)
由于光栅显示器的显示栅格问题, xStep或yStep只能取单位步长这里分为两种情况(为了用更多的离散点):
我们一般通过计算 Δx 和 Δy 来确定xStep和yStep:
(1)如果 |k|<=1,Δx >= Δy ,说明x轴的最大差值大于y轴的最大差值,x轴方向为步进的主方向,
xStep = 1,yStep = k
xi+1=xi+1
yi+1=yi+k
(2)如果 |k|>1,Δy > Δx,说明y轴的最大差值大于x轴的最大差值,y轴方向为步进的主方向,
yStep = 1,xStep = 1 / k。
xi+1=xi+(1/k)
yi+1=yi+1
//DDA算法实现
void LineDDA(int x1,int y1,int xn,int yn)
{
int dm=0;
if(abs(xn-x1)>=abs(yn-y1));
dm=abs(xn-x1);//x为步长方向
else
dm=abs(yn-y1);//y为步长方向
float dx=(float)(xn-x1)/dm;//x为步长方向时,dx值为1
float dy=(float)(yn-y1)/dm;//y为步长方向时,dy值为1
float x=x1;
float y=y1;
for(int i=0;i<dm;i++)
{
putpixel((int)(x+0.5),(int)(y+0.5));//伪代码,绘点
x+=dx;
y+=dy;
}
}
中点画线算法
相比于Breanham算法,中点画线算法更加直接(非效率)。中点算法的重要假设是,我们能绘出没有间隔的最细的直线。两对角像素之间的连接是没有间隔的。
设线段端点为:(x1, y1),(x2, y2),∆x和∆y为水平和垂直偏移量
∆y=y2-y1
∆x=x2-x1
直线的一般方程:
f(x,y)=Ax+By+c=0
其中:A=-(∆y) B=∆x c=-B∆x
f(x,y)=(y1-y2)x+(x2-x1)y+x1y2-x2y1=0
假设已经确定了要显示的点(xk , yk),那么,需要确定下一个点( xk + 1 , yk + 1)是绘制在B(xk+1,yk)还是A(xk+1,yk+1)上,设M为AB的中点,Q为线段与AB边的交点。若Q在M的上面,则应取像素点A作为( xk + 1 , yk + 1 )否则取像素B
平面任一点( x,y)与直线的相对位置可由(x,y)的符号确定:
f(x,y)<0,(x,y)位于直线下方
f(x,y)=0,(x,y)位于直线上
f(x,y)>0,(x,y)位于直线上方
将M点带入直线一般方程
F
(
x
m
,
y
m
)
=
A
x
m
+
B
y
m
+
C
F(x_m,y_m)=Ax_m+By_m+C
F(xm,ym)=Axm+Bym+C
设决策参数p为M点的函数值:
p
=
f
(
x
k
+
1
,
y
k
+
0.5
)
=
A
(
x
k
+
1
)
+
B
(
y
k
+
0.5
)
+
C
p=f(x_k+1,y_k+0.5)=A(x_k+1)+B(y_k+0.5)+C
p=f(xk+1,yk+0.5)=A(xk+1)+B(yk+0.5)+C
此时:
如果p<0;则M点在Q点下方,应取A点
如果p>0;则M点在Q点上方,应取B点
如果p=0;则M点直线上,取AB均可
di+1=di+? 是否能用增量表示来提高效率呢?
d是x,y的线性函数,所以增量计算是可行的
当d<0时
此时d1=d0+A+B
当d>=0时
此时d1=d0+A
当d0为直线第一个像素时,d0在直线上,所以
d0=A+0.5B
总结:
当d<0时 d(new)=d(old)+A+B
当d>=0时 d(new)=d(old)+A
d0=A+0.5B
用2d代替d拜托浮点运算,得出仅包含整数加法运算的算法
void MidPointLine(int x1,int y1,int xn,int yn)
{
int dx,dy,d1,d2,d,x,y;
dx=xn-x1;
dy=yn-y1;
d=dx-2dy; //原初值的d1
dt=2dx-2dy; //原1-k
db=-2dy; //原-k
x=x1;=y1;
putpixel(x,y);
while(x<xn)
{
if(d<0)
{
x++;
y++;
d+=dt;
}
else
{
x++;
d+=db;
}
putpixel(x,y);
}
}
Bresenham画线法
由Bresenham提出的一种精确而有效的光栅线生成算法,可用于显示线、圆和其他曲线的整数增量运算。它是目前最有效的线段生成算法。
基本思想:
该算法的思想时通过各行各列像素中心构造一组虚拟网络线,按照直线起点到中点的顺序,计算直线与各垂直网格线的交点,然后根据误差项的符号确定该列像素中与此交点最近的像素。
初值:d0=0
d=d+k
一旦d>=1,就让它减去1,保证d的相对性以及在0~1之间。
那么,在这种情况下我们是否也可以提升为整数加法提高效率呢?答案是肯定的。
算法步骤:
1.输入直线两端点(x0,y0),(x1,y1)
2.计算初始值dx,dy,e=-dx,x=x0,y=y0
3.绘制点(x,y)
4.e更新为e+2dy,判断e的符号,e>0,(x,y)更新为(x+1,y+1),同时将e更新为e-2dx,否则更新为(x+1,y)
5.直线没有画完,重复3,4,直到结束
补充:
采用增量形式:如果基于第k步 的决策变量dk表示第k+1步的决策变量,可以使算法更加有效:
d
k
+
1
=
d
k
−
2
D
y
(
d
k
>
0
)
d_k+_1=d_k-2Dy(d_k>0)
dk+1=dk−2Dy(dk>0)
d
k
+
1
=
d
k
−
2
(
D
y
−
D
x
)
d_k+_1=d_k-2(Dy-Dx)
dk+1=dk−2(Dy−Dx)
对每个x的值,只需要进行整数加法运算以及测试
//算法实现(仅考虑0<=k<=1的情况)
void BresenhamLine(int x1,int y1,int xn,int yn)
{
int x,y,dx,dy,d,d1,d2;
dx=xn-x1;
dy=yn-y1;
d=2*dy-dx;
d1=2*dy;
d2=2*(dy-dx);
x=x1;
y=y1;
putpixel(x,y);
while(x<xn)
{
x++;
if(d<0)
{d+=d1;}
else
{y+=1;
d+=d2;}
putpixel(x,y);
}
}