代码介绍
在MFC框架中,VS自动创建的框架会包含画图所需的头文件,我们所需要做的是在View中的onDraw()方法里调用画笔(一个CDC类型的指针)来进行绘制;
基本方法介绍:
CDC* pDC = GetDC();//获取画笔
pDc->SetPixel(x, y, RGB(255, 0, 0));//绘制像素,即画一个点
CPoint Pt(0, 0);//实例化一个点对象
DrawLine
画线的方法主要有两种,DDA和Bresenham,这里使用了Bresenham直线算法
代码
void bresenham(CPoint PtBegin, CPoint PtEnd, CDC* pDC){
int x, y, dx, dy, e;
int k;
/*
*保证dx > 0
*/
if (PtEnd.x - PtBegin.x < 0){
CPoint temp = PtBegin;
PtBegin = PtEnd;
PtEnd = temp;
}
dx = PtEnd.x - PtBegin.x;
dy = PtEnd.y - PtBegin.y;
/*
*若判断为竖线,直接绘制,否则执行Bresenham算法
*/
if (dx == 0){
x = PtBegin.x;
y = PtBegin.y;
while (y <= PtEnd.y){
pDC->SetPixel(x,y, RGB(255, 0, 0));
y++;
}
}
else{//先判断|k|<=1的情况
if (abs(dx)>=abs(dy)){
if (dy > 0)//判断dy是否为负,是则取反,按dy>0的情况执行算法,在绘制时再做做过点P0关于x轴平行的直线的对称
{
k = 0;//标注dy是否小于零,是则为1,不是则为0
x = PtBegin.x;
y = PtBegin.y;
}
else{
k = 1;
dy = -dy;
x = PtBegin.x;
y = PtBegin.y;
}
e = 2 * dy - dx;//初始化误差e
while (x <= PtEnd.x){
switch (k)
{
case 0:
pDC->SetPixel(x, y, RGB(255, 0, 0));//dy>0时,正常绘制
break;
case 1:
pDC->SetPixel(x, PtBegin.y * 2 - y, RGB(255, 0, 0));//dy<0时,先做过点P0关于x轴平行的直线的对称再绘制
break;
}
//根据e的不同情况选择不同de,并对下一个点取值做不同判断
if (e<0)
e += 2*dy;
else{
y++;
e += 2 * (dy - dx);
}
x++;
}
}
else{\\|k|>1时,将坐标做关于直线y=x的对称再运行算法
int temp = dy;
dy = dx;
dx = temp;
if (dx > 0)//还是先保证dx>0
{
k = 2;
y = PtBegin.x;
x = PtBegin.y;
}
else{//dx<0,对dx取反
k = 3;
dx = -dx;
y = PtBegin.x;
x = PtBegin.y;
}
e = 2 * dy - dx;//初始化误差e
while (y <= PtEnd.x){
switch (k)
{
case 2://dx>0时,关于直线y=x取反后绘制
pDC->SetPixel(y, x, RGB(255, 0, 0));
break;
case 3://dx<0时,关于直线y=x取反,再做关于直线y=PtBegin.y的对称后绘制
pDC->SetPixel(y, PtBegin.y * 2 - x, RGB(255, 0, 0));
break;
}
if (e < 0)
e += 2 * dy;
else{
y++;
e += 2 * (dy - dx);
}
x++;
}
}
}
}
演算
DDA算法中,由公式 y=k∗x+b 来计算点的坐标,虽然简单易行,但对计算机程序来说,需要处理较多的浮点数运算和取整操作,这显然会影响到程序的运行速度,尤其是针对于对计算机图形学来说作为基本组成元素的直线,更是如此。Bresenham直线算法减少了绘制直线时的浮点数运算以及四舍五入的操作,是一种出色的算法。
首先,针对垂直于x轴或y轴的直线,我们可以简单的绘制。对于其它更普遍的直线,我们先推导直线的隐式方程。
假设已知直线上两点
P0=(x0,y0)
,
P1=(x1,y1)
,且
x0<x1
由直线的两点式可知
移项化简得
由此,设
那么,对任意一点 Q(x,y) ,当 F(x,y)<0 ,点在直线线上部;当 F(x,y)>0 ,点在直线线下部。
再看下图
在这里,我们只考虑直线斜率大于0,小于1的情况,其他情况可由对称得到。
假设现在处于一个光栅格的中心
(i+12,j+12)
,这个光栅格已经着色,现在,我们来到
x=i+32
,设a为
y(i+32)
离
j+32
的距离,b为
y(i+32)
离
j+12
的距离
。设
d=a−b
,
d>0
,选择点
(i+1,j)
着色,否则,选择点
(i+1,j+1)
着色。即
这样,我们将原本的计算问题转化为数值的判断问题,并与上一个像素点相关联。
又因为
d>0⇔F(xn−1+1,yn−1+12)<0
d<0⇔F(xn−1+1,yn−1+12)>0
d>0 说明 j+12 与 j+32 的中点在直线之上
由此,上述问题便可以转化为判断 F(xn−1+1,yn−1+12) 的值
此后,
设 dx=x1−x0 , dy=y1−y0
所以,d初始化为 d=F(x0+1,y0+12)=dx−12∗dy ,此处,为避免出现float类型,将
d = d * 2
,
d = 2*dx - dy
。
下面求d的增量形式:
这里设
ΔN=(1,12)
增量计算时,设
Pn−1=(x,y)
当 d<0 时, Pn=(x+1,y)
当
d>0
时,
Pn=(x+1,y+1)
上面说到,为了避免float类型计算,将d值乘2,故实际计算时
初始化
d=2∗dy−dx
DrawCircle
代码
/*
*画圆的方法来自于Bresenham直线算法的改进
*/
void Draw::DrawCircle(int x0, int y0, int r, CDC* pDc){
int x = 0, y = r, d = 1 - r;
while (y >= x){
pDc->SetPixel(x0 + x, y0 + y, RGB(255, 0, 0));
pDc->SetPixel(x0 + x, y0 - y, RGB(255, 0, 0));
pDc->SetPixel(x0 - x, y0 + y, RGB(255, 0, 0));
pDc->SetPixel(x0 - x, y0 - y, RGB(255, 0, 0));
pDc->SetPixel(x0 + y, y0 + x, RGB(255, 0, 0));
pDc->SetPixel(x0 + y, y0 - x, RGB(255, 0, 0));
pDc->SetPixel(x0 - y, y0 + x, RGB(255, 0, 0));
pDc->SetPixel(x0 - y, y0 - x, RGB(255, 0, 0));
if (d < 0)
d += 2 * x + 3;
else{
d += 2 * (x - y) + 5;
y--;
}
x++;
}
}
演算
为方便书写,这里假设圆的圆心为坐标原点,即(0,0),
同时,依照Bresenham直线算法里的思想,这里将圆等分为八分,公式推导时只考虑y>0,x>0,x<=y的情况,其余部分可由对称得到。
已知圆的基本公式为
x2+y2=r2
,即满足该式的点在圆上。那么考虑点在圆内的情况,此时
x2+y2<r2
;反之,当点在圆外时,
x2+y2>r2
。
由此,设
下面看下图
设
P0=(0,r)
为第一个点,显然
F(P0)=0
。依照Bresenham直线算法的思想,我们设一变量
ΔN
,
ΔN=(1,−12)
。
若
F(P0+ΔN)>0
,说明此时点
P′=P+ΔN
在圆外,此时,我们应该x++
并y--
,以保证点
P1
不会太过偏离圆;反之,若
F(P0+ΔN)<0
,说明此时点
P′=P+ΔN
在圆外,此时,我们应该x++
,以保证点
P1
不会太过偏离圆。
总结来说,即
下面为减少公式
F(Pn−1+ΔN)
的计算量,我们采取增量计算的方式。
首先我们假设一变量d以表示误差,则d可如下初始化
为了在程序中能以整型
int
进行计算,我们在程序中写作
d=1-r
。
增量计算时,设
Pn−1=(x,y)
当 d<0 时, Pn=(x+1,y)
当
d>0
时,
Pn=(x+1,y−1)