图形学课程设计总结

马上要毕业的人的,学校给开了一些奇怪的课程,如java, asp.net, 图形学,人工智能,信息安全.真不知道学校到底怎么想的.这些课程可以说都是很好的课程,学校早干嘛去了,现在才开.郁闷啊,对于图形学,人工智能,信息安全,只能用剩下的时间学个皮毛了,了解了解也是挺不错的..而至于Javaasp.net就算了,一直坚持学习C/C++,现在再去学那些,到毕业也只是个皮毛,对于找工作也没多大帮助,就不在在哪方面下劲了.这不,图形学老师布置了一个任务,实现画直线,画圆,填充,裁剪的算法,并演示出来。花了一段时间做了这个程序。在此做一下总结,因为从里面学到了一些东西.

首先说一下自己做的程序:

界面:

设个界面是动态生成的,包括那个画卷和字体。其中只是用到了定时器和一些画图的东西.

本想弄一只白鸽飞翔着拉开这个画卷的,却不知咋的,图片弄上去

总是不合自己的意思.看多遍都没明白.只好放弃的那个想法.

各个模块:

 (画直线)

(画圆)

 

(填充)

 

 

 

裁剪:

介绍完毕.现在开始自己的总结:

1 设置窗口的图标

 

针对于文档的应用程序:

方法 1:

CmainFrame窗口类中,添加下列代码:

HICON hIcon=AfxGetApp()->LoadIcon(IDI_ICON1);

SetIcon(hIcon,TRUE);

IDI_ICON1 为自定义图标ID.

 

方法 2:

单击Wordspace 窗口中的ResourceView标签,选中资源IDIDR_MAINFRAM图标资源,这个是系统默认的图标资源.默认的图标就是这个。现在我们把它删除掉,然后定义我们自己的图标资源来替换掉他.不过要记得将我们自定义的图标资源的名字改为IDR_MAINFRAM.也可以改为AFX_IDI_STD_MDIFRAME(如果是MDI应用程序)或改为 AFX_IDI_STD_FRAME(如果是SDI应用程序).这两个资源ID是系统预定义了的.然后编译运行就OK.

 

针对于基于对话框的应用程序:

方法 1:

针对于基于对话框的程序用一个更加便宜的方法:

在对话框的构造函数中有这样一句:

m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);

直接将IDR_MAINFRAME换成你自定义的图标ID就可以了。

方法 2:

上面的方法2 同样适用于这里.

 

静态设置图标,上面的两种方法已经很够用了,如果我们想动态的改变图标呢,如点击一个按钮,或是在打开一个对话框的时候改变主窗口的图标.我们可以这样做:

HICON hIcon=AfxGetApp()->LoadIcon(IDI_ICON1);

ASSERT(hIcon);

AfxGetMainWnd()->SendMessage(WM_SETICON,TRUE,(LPARAM)hIcon);

//给系统发送一个WM_SETICON消息.对于前面的那个AfxGetMainWnd()函数,它是一个全局函数,返回值为CWnd*用来获得一个窗口类指针,这个函数可以用到任何地方,因为它是全局的嘛.但在主窗口中我们就不必用这个函数了,应为自己调用自己的函数,当然不用什么对象调用了啊,内部调用嘛.

 

如果朋友不喜欢发送消息,这样使用也是OK.

HICON hIcon=AfxGetApp()->LoadIcon(IDI_ICON1);

ASSERT(hIcon);

AfxGetMainWnd()->SetIcon(hIcon,TRUE);

2 设置窗口背景图片

 

对于这个问题一直是一个很烦人的问题,我一直都是在用填充的方法来解决这个问题.我的方法如下:

CRect rect;

GetClientRect(&rect);

CBitmap bitmap;

bitmap.LoadBitmap(IDB_BITMAP1);

CBrush brush;

brush.CreatePatternBrush(&bitmap);

dc.FillRect(&rect,&brush);

这个方法一直用着还不错,就没另寻它法,不过我在网上看到了这样一篇文章,说的很是不错,这里推荐一下:

 

问题是这样产生的.OnEraseBkGnd,如果你不调用原来缺省 
OnEraseBkGnd只是重画背景则不会有闪烁.而在OnPaint里面, 
由于它隐含的调用了OnEraseBkGnd,而你又没有处理OnEraseBkGnd 
函数,这时就和窗口缺省的背景刷相关了.缺省的 
OnEraseBkGnd操作使用窗口的缺省背景刷刷新背景(一般情况 
下是白刷),而随后你又自己重画背景造成屏幕闪动. 
另外一个问题是OnEraseBkGnd不是每次都会被调用的.如果你 
调用Invalidate的时候参数为TRUE,那么在OnPaint里面隐含 
调用BeginPaint的时候就产生WM_ERASEBKGND消息,如果参数 
FALSE,则不会重刷背景. 
 
所以解决方法有三个半: 
1.OnEraseBkGnd实现,不要调用原来的OnEraseBkGnd函数. 
2.OnPaint实现,同时重载OnEraseBkGnd,其中直接返回. 
3.OnPaint实现,创建窗口时设置背景刷为空 
4.OnPaint实现,但是要求刷新时用Invalidate(FALSE)这样 
的函数.(不过这种情况下,窗口覆盖等造成的刷新还是要闪一 
,所以不是彻底的解决方法) 
都挺简单的. 

 

从这篇文章中我们都应该明白WM_PAINT消息处理函数为什么会使得我们写上去的的文字或是画上去的图片消失的原因的.另外我们还应该明白一个东西,就是很多时候我们在OnPaint()函数中重绘了我们想要的文字或是图片.其他一切正常,并且也达到了我们满意的效果,可是有时窗口会出现闪动的现象.从这篇文章中我们应该明白原因了.即这个函数每次在刷新我们的窗口时默认调用了OnEraseBkGnd,这个函数使用默认的画刷即白色画刷来刷新我们的窗口。至于怎么改变这个画刷,我找到了这样一个函数:

BOOL CreateSysColorBrush( int nIndex );

介绍如下:

Initializes a brush color. The brush can subsequently be selected as the current brush for any device context.

nIndex

Specifies the hatch style of the brush. It can be any one of the following values:

  • HS_BDIAGONAL   Downward hatch (left to right) at 45 degrees
  • HS_CROSS   Horizontal and vertical crosshatch
  • HS_DIAGCROSS   Crosshatch at 45 degrees
  • HS_FDIAGONAL   Upward hatch (left to right) at 45 degrees
  • HS_HORIZONTAL   Horizontal hatch
  • HS_VERTICAL   Vertical hatch

然而不管我如何定义画刷,该画刷都不会被系统使用来刷新背景.不只是何原因.可问题还是被解决了,我众里寻他千”Google” + “百度”.终于让我找到了,原来我们注册窗口类的时候,WNDCLASS结构体中有这么一个成员:

typedef struct _WNDCLASS {

    UINT       style;

    WNDPROC    lpfnWndProc;

    int        cbClsExtra;

    int        cbWndExtra;

    HINSTANCE  hInstance;

    HICON      hIcon;

    HCURSOR    hCursor;

    HBRUSH     hbrBackground;

    LPCTSTR    lpszMenuName;

    LPCTSTR    lpszClassName;

} WNDCLASS, *PWNDCLASS;

我们只需修改这个成员就可以了,在这里我们可以定义我们自己的画刷.WIN32程序下可以这么修改,对于MFC程序就不清楚怎么获得这个结构体了.好啦,言归正传。我们还把话题放到窗口背景的设置上.

1.OnEraseBkGnd实现,不要调用原来的OnEraseBkGnd函数. 
2.OnPaint实现,同时重载OnEraseBkGnd,其中直接返回. 
3.OnPaint实现,创建窗口时设置背景刷为空 
4.OnPaint实现,但是要求刷新时用Invalidate(FALSE)这样 
的函数.(不过这种情况下,窗口覆盖等造成的刷新还是要闪一 
,所以不是彻底的解决方法) 
都挺简单的. 

这个是上面文章中提到的方法,这里仅仅提到了实现的地方,并没有提到实现的方法.有点遗憾,如果哪位朋友知道其他的方法不妨分享一下.

 

3 改变对话框中控件的颜色

这个需要为对话框重载这么一个函数. OnCtlColor(….).

重载这个函数之后我们就会发现系统为我们生成了这样一句话:

// TODO: Return a different brush if the default is not desired

即在这里我们可以创建我们自己的画刷来改变控件的颜色,并返回他即可.这样我们就改变了控件的背景颜色.如果我们想改变特定空间的背景色可以这样做:

       if (pWnd->GetDlgCtrlID() == IDC_DISPLAYLINECOLOR)

       {

              hbr = CreateSolidBrush(m_LineColor);

              pDC->SetBkColor(m_LineColor);

              return hbr;

       }

这个问题的引出是我设置了一个设置颜色的对话框,这个对话框是这样的:

我使用了静态文本来显示用户当前设置的颜色,这样我们就需要为特定的控件设置特定的背景色的.这个功能需要当用户点击颜色设置对话框的确定按钮后,静态文本能够显示用户刚刚设定的颜色,所以那里就需要调用一个:

InvalidateRect(NULL,TRUE);函数来刷新窗口.

如果我们想让空间背景透明可以使用:

pDC->SetBkMode(TRANSPARENT);

设置空间字体颜色:

pDC->SetTextColor(RGB(245,0,0));

 

4 画圆的时候遇到的一个问题

如果哪位朋友用过画图工具的话,你们都应该知道画图工具里画圆可以随着鼠标的移动画圆,即当我们按下鼠标左键并移动的时候就会有圆来响应我们的鼠标的移动,从而给我们提供一个参考.我想在我的画圆功能中提供这样一个功能,当然这个功能需要相应一些消息.关键是实现:

实现方法有两种:

方法 1:

每次鼠标移动时都要刷新窗口,以抹去鼠标上个位置画出来的那个圆,然后画当前位置的圆即可.

方法 2:

   每次鼠标移动的时候,我们都要使用背景色的画笔来重画鼠标上个位置的那个圆,用来抹去。然后再用自定义画刷或默认画刷话当前位置的圆即可.

两种方法看似都很好,可是如你使用过第一种方法后就会知道,它的窗口的刷新率是很大的,以至于我们都无法看清当前位置的圆,除非鼠标停下。所以我选择了使用方法 2.这个方法使用起来很好.所说多做了一些工作,但相比刷新窗口会好很多.

 

5 递归问题

递归是一个很耗费时间和空间的东西,虽说有些问题离了他不行。但若可以使用非递归能解决的问题,我们应该尽量避免使用递归.这里举两个例子就很清楚了:

第一个例子:

四连通递归填充算法:

void FloodPadRegion(CDC *dc, int x, int y)

{

       if (dc->GetPixel(x,y) == RGB(255,255,255))

       {

              dc->SetPixel(x,y,m_PadColor);

              FloodPadRegion(dc,x,y+1);

              FloodPadRegion(dc,x,y-1);

              FloodPadRegion(dc,x-1,y);

              FloodPadRegion(dc,x+1,y);

       }

}

我们使用这个函数去填充局域,如果区域稍微大点,程序就会被自动关闭。原因是它导致大量的递归调用,使栈溢出。为了证明这个问题,我们再来看另一个递归的例子,我们对此算法进行改进,即不再是一个点一次的递归,而是一条线一次的递归,这算法是我对这个算法的改进:

void  ScanfLinePadRegion(CDC *dc, int x, int y)

{

       CPoint pt(x,y);

       int xr,xl;

       while(GetPixel(dc->m_hDC,x,y) == RGB(255,255,255)) //向右填充

       {

              dc->SetPixel(x,y,m_PadColor);

              x++;

       }

       xr = x-1;

       x = pt.x-1;

       while(GetPixel(dc->m_hDC,x,y) == RGB(255,255,255))//向左填充

       {

              dc->SetPixel(x,y,m_PadColor);

              --x;

       }

       xl = x+1;

       x = xl;

       if (GetPixel(dc->m_hDC,xl,pt.y+1) == RGB(255,255,255))

       {

              ScanfLinePadRegion(dc,xl,pt.y+1);

       }

       else

       {

              y = pt.y + 1;

              x = xl;

              while (GetPixel(dc->m_hDC,x,y) != RGB(255,255,255) && x < xr)

              {

                     ++x;

              }

              if (x < xr)

              {

                     ScanfLinePadRegion(dc,x,y);

              }

       }

       if (GetPixel(dc->m_hDC,xl,pt.y-1) == RGB(255,255,255))

       {

              ScanfLinePadRegion(dc,pt.x,pt.y-1);

       }

       else

       {

              y = pt.y-1;

              x = xl;

              while (GetPixel(dc->m_hDC,x,y) != RGB(255,255,255) && x < xr)

              {

                     ++x;

              }

              if (x < xr)

              {

                     ScanfLinePadRegion(dc,x,y);

              }

       }

}

使用这个函数,我用来填充整个屏幕都不会发生栈溢出的现象,由此可见,递归是一个很耗费资源的东西。虽然本函数也使用了递归,但是它比上一个函数改进了很多。其实我觉得更好的方法是书本山的扫描线算法,它使用栈完成了整个填充的过程,虽说他也会占用很多的内存单元,但我认为会比递归好的多.

 

好了,先总结这么多吧,再想出其它问题再补上.

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值