如何在MFC内实现雪花动画(修正补充篇)

       时隔一年了,在MFC内实现雪花飘动效果的设计的更新也停滞了N久,博文《如何在MFC内实现雪花动画》重点讲解了第二种雪花贴图的方式,原理简单易懂广受好评。最近,有读

者反映,第一种实现雪花贴图的方法因为涉及成员数量多,贴图次数多且过程复杂,让人

眼花缭乱。我也只能说,贴图在程序中作为一种高级的操作,原理有时候是只可意会而不

可言传的,必须在掌握基础知识的前提下,在实践中积累经验并最终掌握方法。

      下面,我结合之前CSDN的友人Conry提到过:“没必要用第二个对话框作为背景填充显示。单纯利用dest内存在的背景数据作为支持即可。”将第二种显示动画背景的方案提出,

并根据个人理解,讲解第一种方案内实现动画的原理。


      较第二种方案,第一种方案有2个好处:
1、背景图片拉伸后可以和动画联合在一起,失真少;
2、其创建了直接的动画背景,并且控件边缘和雪花动画融合得天衣无缝。

      第一种方案创造雪花动画,主要和2个函数有关:Init()和Method1()。其中Init()负责进

行拉伸背景的创建和各个贴图成员的初始化,Method1()负责雪花透空处理和动画效果的

产生,最后,通过OnTimer启动动画,并结合OnPaint() 来刷新显示。

      下边讲解一下Init()的原理,
      原先在博文《如何在MFC内实现雪花动画》中,源程序背景显示是通过创建另一个和主动

画窗体CTSnowDlg大小相同,并置于其后的窗体CBkDlg作为虚拟背景载入的。因为有了方

案1,所以得以使用其内的成员CBitmap dest完美地显示背景和动画,只需要做如下改进

即可:

 1、新建一个与背景相对应的画刷成员 CBrush m_bkBrush; 并重载CTSnowDlg::OnCtlColor HBRUSH CTSnowDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) { if ( nCtlColor == CTLCOLOR_STATIC ) { pDC->SetTextColor(RGB(255,255,255)); pDC->SetBkMode(TRANSPARENT); } if ( nCtlColor == CTLCOLOR_EDIT ) { return CDialog::OnCtlColor(pDC, pWnd, nCtlColor); } return (HBRUSH)(m_bkBrush);// 返回和背景相关的画刷 } 2、重载CTSnowDlg::OnInitDialog BOOL CTSnowDlg::OnInitDialog() { CDialog::OnInitDialog(); …… // 注释掉以下代码 // 背景方式1: // 建立一个无模式对话框 用于作为虚拟背景的对话框,与前对话框融合 // Create 内第二参数必须为CWnd::GetDesktopWindow(),否则无法达到想要的 分层效果 //m_pDlg = new CBkDlg; //m_pDlg->Create(IDD_DIALOG_BK, CWnd::GetDesktopWindow()); //m_pDlg->MoveWindow( 0,0, 800,600 ); //m_pDlg->ShowWindow(SW_SHOW); …… // 背景方式2:必须放在此处进行初始化! m_bkBrush.CreatePatternBrush(&dest); return TRUE; // return TRUE unless you set the focus to a control } 3、重载CTSnowDlg::OnPaint void CTSnowDlg::OnPaint() { if (IsIconic()) { ……按默认 ……不改动 } else { CPaintDC dc(this); if(m_bTimer2) dc.BitBlt(0,0, m_cr.Width(), m_cr.Height(), m_mdc2, 0,0, SRCCOPY); else if(m_bTimer1){ CDC memDC; memDC.CreateCompatibleDC( &dc ); CBitmap *pOld = ( CBitmap* )memDC.SelectObject( &dest ); dc.BitBlt( 0, 0, m_cr.Width(), m_cr.Height(), &memDC, 0, 0, SRCCOPY ); memDC.SelectObject( pOld ); } else{// Show the background without the cartoon. CDC memDC; memDC.CreateCompatibleDC( &dc ); CBitmap *pOld = ( CBitmap* )memDC.SelectObject( &dest ); dc.BitBlt( 0, 0, m_cr.Width(), m_cr.Height(), &memDC, 0, 0, SRCCOPY ); memDC.SelectObject( pOld ); } } }

经过以上代码后,再运行程序,可以看到,在程序初始化后弹出的窗体界面,就直接显示

了雪花背景图,和原先利用双层对话框组合实现雪花动画的背景效果一致,见下图1:

如何在MFC内实现雪花动画(修正补充篇)_第1张图片

初始化流程图如下(未完成):

详细代码注释如下:

BOOL CTSnowDlg::Init() { // 载入雪花贴片 & 背景 if( !snow.LoadBitmap(IDB_SNOW) || !bg.LoadBitmap(IDB_BG) ){ MessageBox( _T("LoadBitmap()错误!")); return FALSE; } // 取贴片长、宽 BITMAP bm; snow.GetObject( sizeof(BITMAP), &bm ); m_snowSize.cx = bm.bmWidth; m_snowSize.cy = bm.bmHeight; // 制造雪花蒙板mask贴片进行透空屏蔽 if( !mask.CreateBitmap( bm.bmWidth, bm.bmHeight, 1, 1, NULL) ){ MessageBox( _T("CreateBitmap()错误!")); return FALSE; } CDC *pDC = GetDC(); //必须与ReleaseDC搭配使用 CDC maskDC, snowDC; // 创建2个暂存DC maskDC.CreateCompatibleDC( pDC ); // 将maskDC转化成与pDC兼容的DC snowDC.CreateCompatibleDC( pDC ); // 将snowDC转化成与pDC兼容的DC // maskDC内含雪花蒙板mask贴片 CBitmap *pOldMask = ( CBitmap* )maskDC.SelectObject( &mask ); // snowDC内含snow贴片 CBitmap *pOldSnow = ( CBitmap* )snowDC.SelectObject( &snow ); snowDC.SetBkColor( RGB( 255,0,0 ) ); // 设置snowDC的背景色,用于透空 处理 // maskDC内含 mask+snow maskDC.BitBlt( 0,0, bm.bmWidth,bm.bmHeight, &snowDC, 0,0, SRCCOPY ); // 获取背景图片大小 bg.GetObject( sizeof( BITMAP ), &bm ); // 给dest 创建与窗口一致大小的位图数据空间 if( !dest.CreateCompatibleBitmap( pDC, m_cr.Width(), m_cr.Height() ) ){ ReleaseDC( pDC ); return FALSE; } // 给temp 创建与窗口一致大小的位图数据空间 if( !temp.CreateCompatibleBitmap( pDC, m_cr.Width(), m_cr.Height() ) ){ ReleaseDC( pDC ); return FALSE; } // 经过以上2行,dest与temp内位图数据空间大小与窗体大小一致 // 但必须注意,maskDC内的bg(含背景位图数据)大小也许和窗体大小不一致,需要进 行拉伸处理 snowDC.SelectObject( &temp ); // snowDC内含 snow+temp maskDC.SelectObject( &bg ); // maskDC内含 mask+snow+bg snowDC.SetStretchBltMode( HALFTONE ); // 设置拉伸贴图模式,使得拉伸图 像不失真 // 把maskDC的内容拉伸处理并载入snowDC,snowDC内含 snow+mask+temp+bg snowDC.StretchBlt( 0,0, m_cr.Width(),m_cr.Height(), &maskDC, 0,0, bm.bmWidth,bm.bmHeight, SRCCOPY ); // ************************** 如果换成下边2行中任意一行,会怎么样呢? ***********************************// //snowDC.StretchBlt( 0,0, m_cr.Width(),m_cr.Height(), &maskDC, 0,0, m_cr.Width(),m_cr.Height(), SRCCOPY ); //snowDC.StretchBlt( 0,0, bm.bmWidth,bm.bmHeight, &maskDC, 0,0, bm.bmWidth,bm.bmHeight, SRCCOPY ); // ****************************************************************************** ***********************// // maskDC内含 mask+snow+temp+bg+dest maskDC.SelectObject( &dest ); maskDC.BitBlt( 0,0, m_cr.Width(),m_cr.Height(), &snowDC, 0,0, SRCCOPY ); // 选回旧对象指针,用于释放资源 maskDC.SelectObject( pOldMask ); snowDC.SelectObject( pOldSnow ); ReleaseDC( pDC ); //必须与GetDC搭配使用 // 所有都完成,设置标志为TRUE. // 这个时候,雪花未出现,因为初始化的时候SNOW的exist标志位为0 m_bOK = TRUE; return TRUE; }

      代码注释内,有我重点提示的说明语句//********//,读者不妨试试看,就可以了解这些语句的作用了。

      好,明白了初始化的必要步骤后,我们接下来讲解动画原理。Method1()内,不仅实现了

雪花动画,而且还实现了将雪花图片进行透空的处理。


透空流程图如下(未完成):
详细代码注释如下:

void CTSnowDlg::Method1() { // 粒子系统,制造雪花粒子 if( m_count < NUMOFSNOW ){ // NUMOFSNOW = 150 //srand(time(0)); // 随机种子 m_flakes[m_count].x = rand() % m_cr.right;// 随机x坐标下雪 m_flakes[m_count].y = 0; // 顶坐标 m_flakes[m_count].exist = TRUE; m_count++; } // 用两级的MemDC制造下雪图 CDC memDC1, memDC2; CBitmap *pOld1, *pOld2; CDC *pDC = GetDC(); memDC1.CreateCompatibleDC( pDC ); memDC2.CreateCompatibleDC( pDC ); pOld1 = ( CBitmap* )memDC1.SelectObject( &dest );// 含背景+雪花+蒙版 pOld2 = ( CBitmap* )memDC2.SelectObject( &temp );// 一片黑透明 // memDC2 作为第一级缓冲 // memDC1 作为第二级缓冲 memDC1.BitBlt( 0, 0, m_cr.Width(), m_cr.Height(), &memDC2, 0, 0, SRCCOPY ); // 以下透空雪花图片背景进行贴图处理 memDC2.SelectObject( &snow ); // 选入雪花图片 for( int i = 0; i < NUMOFSNOW; ++i ){ if( m_flakes[i].exist ){ // 雪花*背景 做 异或 运算 memDC1.BitBlt( m_flakes[i].x, m_flakes[i].y, m_snowSize.cx, m_snowSize.cy, &memDC2, 0, 0, SRCINVERT ); memDC2.SelectObject( &mask );// 选入蒙版图片 // 雪花*背景*蒙版 做 与 运算 memDC1.BitBlt( m_flakes[i].x, m_flakes[i].y, m_snowSize.cx, m_snowSize.cy, &memDC2, 0, 0, SRCAND ); memDC2.SelectObject( &snow );// 再次选入雪花 // 做 异或 运算还原其余部分 memDC1.BitBlt( m_flakes[i].x, m_flakes[i].y, m_snowSize.cx, m_snowSize.cy, &memDC2, 0, 0, SRCINVERT ); /**/ // 飘.啊.飘 //srand(time(0)); // 随机种子 if( ( rand() & 1 ) == 0 ) // 设定雪花左右摆动的幅度 ,不宜过大 m_flakes[i].x += rand() % 5; else m_flakes[i].x -= rand() % 5; m_flakes[i].y += 5; // 设定雪花往下漂移的幅度 if( m_flakes[i].y > m_cr.bottom ){ // 到底了, 从头再来 m_flakes[i].x = rand() % m_cr.right; m_flakes[i].y = 0; } } } // 释放资源 memDC1.SelectObject( pOld1 ); memDC2.SelectObject( pOld2 ); ReleaseDC( pDC ); }

      以上流程和方法,同样可以扩展延伸到控件方面和动画雪花相互融合的场合,读者在运行

本程序的时候,一定发现了方案1和方案2中雪花在左上角的皇冠中飘落时候的差别,见下

图2:

如何在MFC内实现雪花动画(修正补充篇)_第2张图片

如何在MFC内实现雪花动画(修正补充篇)_第3张图片

      对,这就是Method1()的魅力之二。同理子类化控件,构成类TransparentImage,void TransparentImage::OnPaint()内的代码结构,即其透空原理和void

CTSnowDlg::Method1()一致。
      所不同的是,void CTSnowDlg::Method1()内memDC1作用等同于void

TransparentImage::OnPaint()中的destDC;memDC2的兼并了负责载入Mask图 和 image图的功能,需要多次SelectObject,而void TransparentImage::OnPaint()中,则是区分了蒙版图和image图,分别用不同的DC变量载入,经过一系列复杂的内存贴图处理,最后通过BitBlt绘制到真正的现实设备CPaintDC dc上显示出来。这样一来,我们只是看到了最后的效果,变化的过程看不到,闪烁自然就没了。

      另外一个值得注意的地方在于,为了使控件、雪花、主对话框背景能紧密结合,必须在其

内贴图缓冲的时候通过以下代码取得主对话框的目标位图的指针pBmp,并且,为了使得透

空贴图位置更准确,必须获取控件坐标位置并转换成相对于主对话框的(为什么?读者可

以自己先思考),否则,当控件位置改变的时候,其透空会失败:

CTSnowDlg *pDlg = ( CTSnowDlg* )GetParent();// 取得主对话框的指针pDlg CDC tempDC; tempDC.CreateCompatibleDC( &dc ); CBitmap *pBmp = &pDlg->dest; // 取得主对话框的目标位图 的指针pBmp CBitmap *pOldTemp = ( CBitmap * )tempDC.SelectObject( pBmp ); CRect temp; //******************* 注意,下边2句不等效于:GetClientRect(&temp)! *******************// GetWindowRect( &temp ); pDlg->ScreenToClient( &temp ); // 转换成相对于对话框的坐 标 //**************************************************************************** ********//


      至此,MFC内实现雪花动画的原理讲解完成,如有任何疑问的地方,欢迎各位在我的博文

内提出。如哪位有更巧妙的方法或建议,也可以以邮件的方式和本人一起讨论,以便不断

改进和创新。

你可能感兴趣的:(mfc,制造,image,dialog,扩展,null)