CG : Draw Line And Draw Circle

代码介绍

在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=kx+b 来计算点的坐标,虽然简单易行,但对计算机程序来说,需要处理较多的浮点数运算和取整操作,这显然会影响到程序的运行速度,尤其是针对于对计算机图形学来说作为基本组成元素的直线,更是如此。Bresenham直线算法减少了绘制直线时的浮点数运算以及四舍五入的操作,是一种出色的算法。

首先,针对垂直于x轴或y轴的直线,我们可以简单的绘制。对于其它更普遍的直线,我们先推导直线的隐式方程。
假设已知直线上两点 P0=(x0,y0) P1=(x1,y1) ,且 x0<x1 由直线的两点式可知

yy0y1y0=xx0x1x0

移项化简得
y=y1y0x1x0(xx0)+y0

由此,设

F(x,y)=(y1y0)(xx0)+(x1x0)(y0y)

那么,对任意一点 Q(x,y) ,当 F(x,y)<0 ,点在直线线上部;当 F(x,y)>0 ,点在直线线下部。

再看下图
Bresenham's line algorithm
在这里,我们只考虑直线斜率大于0,小于1的情况,其他情况可由对称得到。
假设现在处于一个光栅格的中心 (i+12,j+12) ,这个光栅格已经着色,现在,我们来到 x=i+32 ,设a为 y(i+32) j+32 的距离,b为 y(i+32) j+12 的距离
。设 d=ab , d>0 ,选择点 (i+1,j) 着色,否则,选择点 (i+1,j+1) 着色。即

PnPn1={(1,0)(1,1)if d > 0 if d < 0 

这样,我们将原本的计算问题转化为数值的判断问题,并与上一个像素点相关联。

又因为
d>0F(xn1+1,yn1+12)<0
d<0F(xn1+1,yn1+12)>0

d>0 说明 j+12 j+32 的中点在直线之上

由此,上述问题便可以转化为判断 F(xn1+1,yn1+12) 的值

此后,

d=F(xn1+1,yn1+12)

dx=x1x0 dy=y1y0
所以,d初始化为 d=F(x0+1,y0+12)=dx12dy ,此处,为避免出现float类型,将 d = d * 2d = 2*dx - dy

下面求d的增量形式:
这里设 ΔN=(1,12)
增量计算时,设 Pn1=(x,y)

Δd=F(Pn+ΔN)F(Pn1+ΔN)

d<0 时, Pn=(x+1,y)
Δd=F(Pn+ΔN)F(Pn1+ΔN)=[(x+1+1x0)dy+(y0(y+12))dx][(x+1x0)dy+(y0(y+12))dx]=dy

d>0 时, Pn=(x+1,y+1)

Δd=F(Pn+ΔN)F(Pn1+ΔN)=[(x+1+1x0)dy+(y0(y+12+1))dx][(x+1x0)dy+(y0(y+12))dx]=dydx

上面说到,为了避免float类型计算,将d值乘2,故实际计算时
初始化 d=2dydx

Δd={2(dydx)2dyif d_{k-1} > 0if d_{k-1} < 0

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
由此,设

F(x)=x2+y2r2
F(x)>0,
F(x)<0,

下面看下图
Bresenham circle

P0=(0r) 为第一个点,显然 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 不会太过偏离圆。

总结来说,即

PnPn1={(1,0)(1,1)if F(P_{n-1} + ΔN) < 0if F(P_{n-1} + ΔN) > 0

下面为减少公式 F(Pn1+ΔN) 的计算量,我们采取增量计算的方式。
首先我们假设一变量d以表示误差,则d可如下初始化

d=F(P0+ΔN)F(P0)=F(P0+ΔN)=12+12+r2=54r

为了在程序中能以整型 int进行计算,我们在程序中写作 d=1-r

增量计算时,设 Pn1=(x,y)

Δd=F(Pn+ΔN)F(Pn1+ΔN)

d<0 时, Pn=(x+1,y)
Δd=F(Pn+ΔN)F(Pn1+ΔN)=[(x+1+1)2+(y12)2][(x+1)2+(y12)2]=2x+3

d>0 时, Pn=(x+1,y1)

Δd=F(Pn+ΔN)F(Pn1+ΔN)=[(x+1+1)2+(y112)2][(x+1)2+(y12)2]=2x2y+5

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值