二维基本图形生成过程
图元的生成也就是指图元的参数形式到点阵形式的转换,参数形式可理解为数学上所学的代数表达式,而点阵形式则用于计算机显示,光栅图形显示器可以看成是一个像素的矩阵。
由上图我们可以看出来二维基本图形生成的大致流程。基本图元经过二维裁剪再经过扫描转化即可进行显示。在这篇文章当中,会介绍直线,圆和椭圆的二维图形扫描转化算法。
直线段的扫描转化
首先我们要明确如何来提高算法效率,这里可以用到的提高算法效率的方法:
- 将乘法转化为加法,加法的运算效率远大于乘法
- 将浮点数加法转化为整数加法
- 改进直线方程的类型
- 利用直线的连续性进行算法优化
DDA算法
算法思想:
该算法利用了增量思想,这是二维基本图形生成一个很重要的算法。
利用斜截式方程
y
i
=
k
x
i
+
b
y
i
+
1
=
k
x
i
+
1
+
b
y_i=kx_i+b\\ y_{i+1}=kx_{i+1}+b
yi=kxi+byi+1=kxi+1+b
通过运算我们可以的到
y
i
+
1
=
k
x
i
+
1
+
b
y
i
+
1
=
k
(
x
i
+
1
)
+
b
y
i
+
1
=
k
x
i
+
k
+
b
y
i
+
1
=
y
i
+
k
y_{i+1}=kx_{i+1}+b\\y_{i+1}=k(x_i+1)+b\\y_{i+1}=kx_i+k+b\\y_{i+1}=y_i+k
yi+1=kxi+1+byi+1=k(xi+1)+byi+1=kxi+k+byi+1=yi+k
通过这一转换,我们就可以得到DDA算法的基本思想:
当
∣
k
∣
<
1
,
x
=
x
+
1
y
=
y
+
k
|k|<1,\begin{aligned}x=x+1\\y=y+k\end{aligned}
∣k∣<1,x=x+1y=y+k
这里要注意,求出的y要进行取整操作,也就是int(y+0.5)即可。
但为什么是|k|<1呢?
当|k|>1时我们可以尝试计算,所得到的光栅点很稀,没有办法得到连续的直线。因此在|k|>1时我们可以考虑将线进行翻转,也就是
∣
k
∣
>
1
,
y
=
y
+
1
x
=
x
+
1
k
|k|>1,\begin{aligned}y=y+1\\x=x+\frac{1}{k}\end{aligned}
∣k∣>1,y=y+1x=x+k1
代码实现:
void CNewBezierView::OnDdaline_draw(float x1,float x2,float y1,float y2,COLORREF col)
{
CDC *pDC=GetDC();
double k;
double deltx,delty;
double x,y;
if(fabs(x2-x1)>=fabs(y2-y1))
length=fabs(x2-x1);
else
lenth=fabs(y2-y1);
deltx=(x2-x1)/length;
delty=(y2-y1)/length;
x=x1,y=y1,k=1;
while(k<=length)
{
pDC->SetPixel(x,y,col);
x=x+deltax;
y=y+deltay;
k=k+1;
}
}
中点画线法
中点画线法是对于DDA算法的改进算法,在DDA算法当中虽然没有乘法,但是存在浮点数的加法,中点画线法就是通过对直线方程的类型进行改进从而将浮点数的加法变为整数的加法。
算法思想:
在DDA算法中,运用的是直线的斜截式方程,在中点画线法中,改变为直线的一般方程,即为
A
x
+
B
y
+
C
=
0
A
=
−
Δ
y
,
B
=
Δ
x
,
C
=
−
B
Δ
x
Ax+By+C=0\\A=-\Delta{y},B=\Delta{x},C=-B\Delta{x}
Ax+By+C=0A=−Δy,B=Δx,C=−BΔx
先考虑斜率小于1的情况。我们知道,在绘制直线的时候,确定了一个点A之后,下一个点必为B,C中的一个点,是B还是C在于这条直线在x=2的时候,到底是靠近B还是靠近C,这就是中点画线法的基本思路。
下面的问题就转化为,我们如何抉择是取B还是取C。这时候就要引入B与C的中点,我们称为D。将中点带入直线的方程,与直线本身进行比较,我们就可以知道直线与x=2的交点,是位于中点之上,还是中点之下,从而就可以判断我们到底取B还是取C。
由此我们可以得到如下的表达式
d
i
=
A
(
x
i
+
1
)
+
B
(
y
i
+
0.5
)
+
C
d_i=A(x_i+1)+B(y_i+0.5)+C
di=A(xi+1)+B(yi+0.5)+C
若di<0,说明中点位于直线下方,也就是直线更靠近C点,则
d
i
+
1
=
A
(
x
i
+
1
+
1
)
+
B
(
y
i
+
1
+
0.5
)
+
C
=
A
(
x
i
+
1
+
1
)
+
B
(
y
i
+
1
+
0.5
)
+
C
=
d
i
+
A
+
B
d_{i+1}=A(x_{i+1}+1)+B(y_{i+1}+0.5)+C\\ =A(x_i+1+1)+B(y_i+1+0.5)+C\\=d_i+A+B
di+1=A(xi+1+1)+B(yi+1+0.5)+C=A(xi+1+1)+B(yi+1+0.5)+C=di+A+B
若di>0,说明中点位于直线上方,也就是直线更靠近B点,则
d
i
+
1
=
A
(
x
i
+
1
+
1
)
+
B
(
y
i
+
1
+
0.5
)
+
C
=
A
(
x
i
+
1
+
1
)
+
B
(
y
i
+
0.5
)
+
C
=
d
i
+
A
d_{i+1}=A(x_{i+1}+1)+B(y_{i+1}+0.5)+C\\ =A(x_i+1+1)+B(y_i+0.5)+C\\=d_i+A
di+1=A(xi+1+1)+B(yi+1+0.5)+C=A(xi+1+1)+B(yi+0.5)+C=di+A
综上所述,
d
n
e
w
=
{
d
o
l
d
+
A
+
B
,
d
<
0
d
o
l
d
+
A
,
d
>
=
0
d
0
=
A
+
0.5
B
d_{new}=\left\{\begin{aligned}d_{old}+A+B,& &d<0\\d_{old}+A,& &d>=0\end{aligned}\right.\\d_0=A+0.5B
dnew={dold+A+B,dold+A,d<0d>=0d0=A+0.5B
可以通过对d0乘以2来消除浮点数,以达到提高算法效率的结果。
代码实现:
void CNewBezierView::OnMidline_draw(float x1,float x2,float y1,float y2,COLORREF col)
{
CDC *pDC=GetDC();
double x,y,A,B,deltx,delty,d,temp;
deltx=x2-x1;delty=y2-y1;
A=-delty;B=deltx;
if(A*B<0)//斜率为正
{
if(fabs(delty)>fabs(deltx))//k>1
{
d=2*A+B;
x=x1;y=y1;
while(y<=y2)//线段绘制结束条件
{
pDC->SetPixel(x,y,col);
if(d>=0)//取右上方的点
{
d=d+A+B;
x++;
}
else//取正上方的点
d=d+B;
y++;
}
}
else//k<0
{
d=2*A+B;
x=x1;y=y1;
while(x<=x2)//结束条件
{
pDC->SetPixel(x,y,col);
if(d<0)//取右上方的点
{
d=d+A+B;
y++;
}
else//取右方的点
d=d+A;
x++;
}
}
}
else//斜率为负
{
if(fabs(delty)>fabs(deltx))
{
if(B==0&&y2>y1)
{
temp=y1;y1=y2;y2=temp;
A=-A;
}
d=2*A+B;
x=x1;y=y1;
while(y>=y2)
{
pDC->SetPixel(x,y,col);
if(d<0)
{
d=d+A-B;
x++;
}
else
d=d-B;
y--;
}
}
else
{
d=2*A-B;
x=x1;y=y1;
while(x<=x2)
{
pDC->SetPixel(x,y,col);
if(d>=0)
{
d=d+A-B;
y--;
}
else
d=d+A;
x++;
}
}
}
ReleaseDC(pDC);
}
Bresenham算法
DDA算法和中点算法都依赖直线方程实现直线生成,而Brensenham算法的这一思想具有普遍性,不仅仅局限于实现直线的画法。
算法思想:
在中点算法中我们知道当斜率绝对值小于1时,x每增加1,y要么不变,要么增加1,而对y进行判断,在Bresenham算法当中依赖于图中的d值,我们很容易得到,如果d<0.5,则下一个点的y值不变,如果d>0.5,则下一个点y值增加1。
在这个算法当中,最重要的应该就是d值了,但为了与0.5进行比较,d要一直保持在0~1的范围之内,当d值增到1以上,要进行减1操作。
为了提高算法效率,将浮点加法转化为整数加法,我们可以令e=d-0.5,等式两边同时乘以2,这样每次e就只用和0进行比较,不用引入0.5这个小数。每一次e都要加上斜率k,为了使k值也为整数,我们可以再将等式两边同时乘以△x即可:
e
0
=
−
Δ
x
e
i
+
1
=
e
i
+
2
y
i
f
(
e
i
>
0
)
,
e
=
e
−
2
Δ
x
e_{0}=-\Delta{x}\\e_{i+1}=e_i+2y\\if(e_i>0),\ e=e-2\Delta{x}
e0=−Δxei+1=ei+2yif(ei>0), e=e−2Δx
代码实现:
void CNewBezierView::OnBresenhamline_draw(float x1,float x2,float y1,float y2,COLORREF col)
{
CDC *pDC=GetDC();
int e;
int x,y,deltx,delty,k,j,length=1;
deltx=x2-x1;delty=y2-y1;
k=2*deltx;
j=2*delty;
e=-deltx;
x=x1;y=y1;
if(deltx*delty>0)
{
if(fabs(delty)>fabs(deltx))
{
e=-delty;
while(length<=delty)//用Length记录,因为x的数值不能保证
{
pDC->SetPixel(x,y,col);
y++;
e=e+k;
if(e>0)
{
x++;
e=e-j;
}
length++;
}
}
else
while(length<=deltx)//用Length记录,因为x的数值不能保证
{
pDC->SetPixel(x,y,col);
x++;
e=e+j;
if(e>0)
{
y++;
e=e-k;
}
length++;
}
}
else
{
if(fabs(delty)>fabs(deltx))
{
e=-delty;
while(length<=fabs(delty))//用Length记录,因为x的数值不能保证
{
pDC->SetPixel(x,y,col);
y--;
e=e+k;
if(e>0)
{
x++;
e=e+j;
}
length++;
}
}
else
while(length<=deltx)
{
pDC->SetPixel(x,y,col);
x++;
e=e-j;
if(e>0)
{
y--;
e=e-k;
}
length++;
}
}
ReleaseDC(pDC);
}