菜鸟的图形学作业(VC++ 2005)

 

这几天在做计算机图形学的作业。这门课程是本学期的专业限选课,涉及一些OpenGL库的使用和开发。

上周的作业是用MFC实现一个四连通填充算法。要求是最后能实现一个程序,可以在里面画多边形,并用某种实现对其进行四连通填充。

做的过程有些许心得,记之。

首先是画多边形的实现。

由于只是要求直线组成的图形没有要求曲线,我采用了一种比较简单的方法:非可拖曳线。即没有windows自带画板程序里的那种你画线时可以先定一端,另一端点可以自由拖曳并能在你最终落点前实时显示功能。画线时在确定两边端点前没有线条的显示。我采用的实现方法是重写OnLButtonDown函数,监控点击客户区的操作,每点击一下,把该点信息存入一个数组。然后用一个Invalidate()强制重绘;另一方面,在OnDraw函数中遍历数组并连点成线。

这一步实现起来比较简单,后面的填充算法就比较郁闷了,稍微折腾了几下。

首先要解决的是如何响应的问题,因为画线和填充都是监控客户区的鼠标单击事件。如何区分?后来我设了一个flag标志,当选择了填充命令后,flag标志被置1,则监听把监听到的点作为种子点。否则继续画图。

由于上课时老师提示过是用栈的数据结构,想想也是,栈确实是最符合要求的扩散算法。打开MSDN查询有关栈的成员函数,然后在视图类的头文件中包含进STL的stack使用stack。

这里有几个地方需要注意:一个是需要声明使用std名空间,二是为了声明stack而添加视图类成员变量时指定变量类型应该是一个指针:stack<CPoint>*,而不能是stack<CPoint>。而我甚至在变量对大小写敏感这点上还磕了下。

做填充的主体函数时我原想继续顺着画线的思路做,我是这样考虑的:另建一个CArray存放所有所谓的内部点。首先把种子店点压入栈,然后开始读栈顶点,添加到待着色点数组。接着检测它的四个邻点,将该四点与边界点数组进行遍历比较查找,若没找到,说明没有碰触到边界,可以着色,压入栈。当栈为空时,调用Invalidate()强制重绘。

其实一边写这个函数一边就在心里发毛:这个算法实在是太残了。栈要开销,待着色点存放到数组要开销,每个点的四邻点是否碰触边界需要遍历边界点数组要相当的时间开销,而且可怕的是这些步骤的重复次数最坏情况是一个客户区上的所有的像素!而且,这样的实现方法有一个缺点:总是要等到计算完所有的待着色点才一次性将其着色。

抱着试一试的想法,也主要想检测下前面画的线条是否是闭合的,我还是硬着头皮写下来了。一编译运行,果然,停在那里一直算一直算了……既没反应也不响应……

这是当时写的代码:
void CGraphic_1View::allPointsToBeFilled(CPoint oriPoint)
{
    m_stack->push(oriPoint);//原始点
    CPoint nowp,lp,rp,up,dp;//当前点以及四周点
    int lps=0,rps=0,ups=0,dps=0;//状态标志
    while(!m_stack->empty())
    {
        nowp=m_stack->top();
        innerArray->Add(nowp);
        m_stack->pop();//去栈顶点
        lp.SetPoint(nowp.x-1,nowp.y);
        rp.SetPoint(nowp.x+1,nowp.y);
        up.SetPoint(nowp.x,nowp.y-1);
        dp.SetPoint(nowp.x,nowp.y+1);//四周点
        for(int i=0;i<pointArray->GetSize();i++)
        {
            nowp=pointArray->GetAt(i);
            if(nowp==lp)lps=1;
            if(nowp==rp)rps=1;
            if(nowp==up)ups=1;
            if(nowp==dp)dps=1;
        }//边界鉴定
        if(lps==0)
            m_stack->push(lp);
         if(ups==0)
            m_stack->push(up);
          if(rps==0)
            m_stack->push(rp);
           if(dps==0)
            m_stack->push(dp);//内部点入栈
       lps=0;
       ups=0;
       rps=0;
       dps=0;
    } 
}
如果调用和扩充OnDraw函数,就必然有这么多的步骤需要运算。想要优化,只能换一种思路:不用OnDraw函数和强制重绘的搭配来完成,而是在填充函数内部直接操纵pDC设备环境类的指针。而且由于得到设备环境类指针后可以得到实时的客户区数据,边界检查随之可以用一种更直接的方法:检查该点是否已被设置像素颜色。

由于关于栈的操作顺序与前面的相同,故遍历点的算法还是都在的。改动后运行,成功。

改动后的填充函数代码:

void CGraphic_1View::allPointsToBeFilled(CPoint oriPoint)//看 函数名都没改 呵呵
{
    CDC   *cdc;  
    cdc=this->GetDC();
    {
    cdc->SetPixel(oriPoint,fillColor);

    m_stack->push(oriPoint);//原始点
    CPoint nowp,lp,rp,up,dp;//当前点以及四周点
    while( ! m_stack->empty())
    {
        nowp=m_stack->top();
    // innerArray->Add(nowp);
        {
        cdc->SetPixel(nowp,fillColor);
        m_stack->pop();//去栈顶点
        lp.SetPoint(nowp.x-1,nowp.y);
        rp.SetPoint(nowp.x+1,nowp.y);
        up.SetPoint(nowp.x,nowp.y-1);
        dp.SetPoint(nowp.x,nowp.y+1);//四周点
            if(cdc->GetPixel(lp)==bgColor)m_stack->push(lp);
            if(cdc->GetPixel(up)==bgColor)m_stack->push(up);
            if(cdc->GetPixel(rp)==bgColor)m_stack->push(rp);
            if(cdc->GetPixel(dp)==bgColor)m_stack->push(dp);  //内部点入栈
        }
    }
    }
}


然后加上边界检查。包括已着色块检查、边界点检查、客户区边界检查。

写客户区边界检查的时候,简化了一下检查步骤:直接检查栈中是否有客户区原点(0,0),能用这样的简化是因为我并不是要确切的判定种子点不在封闭图形内,我只想让它在还能忍受的时间前停下来……

说起来简单其实写好客户区边界检查后出现了一个情况:之后将不断出现对话框,加了return,又来了另个问题,在一次到边界后,之后每次都将错误地检测出触到客户区边界。花了十多分钟调试,突然想起:当检查到当前点是原点后,它还留在栈里啊!恍然大悟,于是在每次压种子点入栈前先清栈,一编译运行,果然是这问题。呵呵

最终版主函数代码:

void CGraphic_1View::allPointsToBeFilled(CPoint oriPoint)//看 函数名都没改 呵呵
{
    CDC   *cdc;  
    cdc=this->GetDC();
    if(cdc->GetPixel(oriPoint)!=bgColor)
        MessageBoxW(CString("种子点在图形边界或该色块已填充,/n/n请重新选择种子点或清除屏幕后重新操作。"),CString("错误"),0);
    else
    {
    cdc->SetPixel(oriPoint,fillColor);
    while(! m_stack->empty())
        m_stack->pop();

    m_stack->push(oriPoint);//原始点
    CPoint nowp,lp,rp,up,dp;//当前点以及四周点
    while( ! m_stack->empty())
    {
        nowp=m_stack->top();
        //innerArray->Add(nowp);
        if(nowp.x==0||nowp.y==0)
        {
            m_stack->pop();
            MessageBoxW(CString("请选择封闭图形内部的点进行填充"),CString("错误"),0);     
            Invalidate();
            return;
        }//检测碰触客户区的异常
        else{
        cdc->SetPixel(nowp,fillColor);
        m_stack->pop();//去栈顶点
        lp.SetPoint(nowp.x-1,nowp.y);
        rp.SetPoint(nowp.x+1,nowp.y);
        up.SetPoint(nowp.x,nowp.y-1);
        dp.SetPoint(nowp.x,nowp.y+1);//四周点
            if(cdc->GetPixel(lp)==bgColor)m_stack->push(lp);
            if(cdc->GetPixel(up)==bgColor)m_stack->push(up);
            if(cdc->GetPixel(rp)==bgColor)m_stack->push(rp);
            if(cdc->GetPixel(dp)==bgColor)m_stack->push(dp);  //内部点入栈
        }
    }
    }
}

____________________________________________________   更多 http://Yecols.cn

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值