实现QQ的抓图功能

 

最近没写什么,稍稍有点忙,再加上研读modern C++ design太考验脑细胞了,所以赶紧随便研究个什么来继续充实我这个blog……

 

进入正题,如何实现像QQ或者红蜻蜓或者lava-lava那样的抓图功能呢?

根据下面的分析已经实现了这个程序,可是不知道怎么跟大家共享,

如果有兴趣留下Email,我给你把源程序发过去,收到后请来确认一下,谢谢。整个项目的开发环境是VS2005,中文、英文、专业版、Team suit for developer版本都编译测试通过。

时间仓促,因为只是实验性的程序,代码比较乱,注释比较少,还有很多需要改进的地方……请谅解,最近项目比较忙。

 

分析

经过“苦苦”思索和试验,发现了其中的奥妙,本来神秘的功能原来是个纸老虎!

让我们一起来分析一下:

1.  当我点击截图按钮(或者按快捷键)截图,发现整个屏幕都不动了(别说我废话阿!),经过观察屏幕上一直不断动的一些gif动画也静止了。

2.  我们可以在截图上挥动鼠标(会出现一个选择框)圈起一块,然后图片自动就放到聊天的输入框中。

3.  截图的过程中会有一些漂浮的提示文字在画面上,如果截图的时候选上了这些文字,截出来的图片却没有这些提示文字。

首先想,可不可以让桌面静止,并在上面画东西呢?可能可以,我资质愚笨没有找到方法。

 

根据现象1,可以推测我们截图后所看到的是一个静止的图片,那么怎么给用户看到图片呢?Windows程序只能在自己的窗口上画东西,就是说我们所看到的截图是被显示在一个窗口上!

如何验证这个推测呢,我在截图画面尝试按下了Alt+Tab键,令人兴奋的是看到了我们所熟悉的窗口切换选择的Windows对话框,这说明目前看到的确实是一个窗口,图片就显示在这个窗口里面!

那么用鼠标在这个窗口上面选择一块区域的时候,我们所看到的选择框实际上就是在这个窗口上绘制的。截图后图片被放到剪切板中,粘贴到合适的窗口中。

现象3很容易解释,有一种方案是抓图的时候记住用户所选择的坐标,然后从刚才抓的桌面的那个位图上把这块截下来,当然不会有后来绘制的图案或者文字了。

实现

好,经过上面的分析,经过不断的实验我已经做出了一个可以很好工作的MFC程序,在此跟大家分享一下很多有趣的细节和一些零散的技术。

用到的技术

GDI+ & GDI 实现抓图绘制等功能。自认为对GDI了解过于肤浅,所以能采用GDI+就尽量使用。

创建全屏顶置的窗口 用来显示用户截图,以及在上面的操作。

全局键盘钩子 实现快捷键截图。

托盘 & 托盘气球 恩…… 酷一点,用起来不碍眼。

上面提到的技术很有趣、也很简单,稍稍懂VC的人很容易就能学会。下面我会详细地介绍这些技术。

托盘

请参考我的另一篇blog,关于托盘的使用,比较容易。

http://blog.csdn.net/smalllixin/archive/2007/09/13/1783133.aspx

实现

好,经过上面的分析,经过不断的实验我已经做出了一个可以很好工作的MFC程序,在此跟大家分享一下很多有趣的细节和一些零散的技术。

用到的技术

GDI+ & GDI 实现抓图绘制等功能。自认为对GDI了解过于肤浅,所以能采用GDI+就尽量使用。

创建全屏顶置的窗口 用来显示用户截图,以及在上面的操作。

全局键盘钩子 实现快捷键截图。

托盘 & 托盘气球 恩…… 酷一点,用起来不碍眼。

上面提到的技术很有趣、也很简单,稍稍懂VC的人很容易就能学会。下面我会详细地介绍这些技术。

托盘

请参考我的另一篇blog,关于托盘的使用,比较容易。

全局键盘钩子

Windows的全局钩子需要定义在DLL中,其原因大概是需要一个被所有进程共享的数据区吧,通过link中的SHARE来声明,一会将会看到具体做法。

声明一下,这段代码是根据《精通MFC》中介绍钩子那一节稍加修改得来的。

代码详细请在我给你的程序中看。

注意 在连接器->命令行->附加选项中有

/SECTION:SHARDATA,SRW

 

全局键盘钩子

Windows的全局钩子需要定义在DLL中,其原因大概是需要一个被所有进程共享的数据区吧,通过link中的SHARE来声明,一会将会看到具体做法。

声明一下,这段代码是根据《精通MFC》中介绍钩子那一节稍加修改得来的。

代码详细请在我给你的程序中看。

注意 在连接器->命令行->附加选项中有

/SECTION:SHARDATA,SRW

 

创建全屏顶置的窗口

这个技术比较简单,您只需要了解一点背景知识。

Windows窗口有一个层次顺序,叫做Z序,英文名Z order。表示窗口之间的层次关系,把窗口放在最前面就是在Z序的最上面。

这里用一个API改变窗口Z序并扩大这个对话框到全屏幕(注意这是一个无模式对话框)

     int screenWidth = GetSystemMetrics(SM_CXSCREEN);

     int screenHeight = GetSystemMetrics(SM_CYSCREEN);

::SetWindowPos(screenDlg, HWND_TOPMOST, 0, 0, screenWidth, screenHeight,SWP_HIDEWINDOW);

这个函数将对话框扩张至全屏,并让这个对话框一直在最顶端,初始状态为隐藏,你可以之后调用ShowWindow将这个对话框显示出来并激活。无模式对话框的创建与显示这里就不详细说了,网上有很多的资源。

抓图技术

我尽量使用GDI+,有些GDI+解决不了的情况(或许是我不知道)用GDI的函数来处理。

下面的程序段是抓全屏的代码。

 

     int  screenWidth  =  GetSystemMetrics(SM_CXSCREEN);
    
int  screenHeight  =  GetSystemMetrics(SM_CYSCREEN);

    Bitmap
*  bmp  =  :: new  Bitmap(screenWidth, screenHeight,PixelFormat32bppARGB);
    Graphics g(bmp);
    HDC hDC 
=  g.GetHDC();
    HDC hDesktopDC 
=  ::GetDC(HWND_DESKTOP);
    BitBlt(hDC, 
0 0 , screenWidth, screenHeight, hDesktopDC,  0 0 , SRCCOPY);
    g.ReleaseHDC(hDC);

    ::ReleaseDC(HWND_DESKTOP, hDesktopDC);

 

   

其中calculateRect函数是自己定义的,他根据用户鼠标左键落下坐标和抬起坐标计算出一个Rect对象,PasteBitmapToClipboard函数是将这个Bitmap对象复制到剪切板,这个函数很让我不爽,他的存在是因为当我尝试把GDI+中Get到的Handle放到剪切板上的时候总是失败,所以只有用一系列GDI函数来完成这个操作,这个方法还是我在google国外的一个论坛中找到的。
BOOL CScreenCapDlg::PasteBitmapToClipboard( Gdiplus::Bitmap *  gdipBitmap )
{
    HBITMAP hBitmap;
// HBITMAP created from your Gdiplus Bitmap (i.e., screenBmp) 

    gdipBitmap
->GetHBITMAP(Color(255,0,0,0), &hBitmap);

    CBitmap bitmap;
// temp bitmap 
    CClientDC dcClient(this);// client DC from your window handle 

    CDC dcDest;
// Destination DC (i.e., memory dc for your temporay bitmap ) 
    dcDest.CreateCompatibleDC(&dcClient);

    CDC dcSource;
// Source DC (i.e., memory dc for your Gdiplus bitmap ) 
    dcSource.CreateCompatibleDC(&dcClient);

    
// Select your Gdiplus HBITMAP into the memory DC 
    HBITMAP oldhBitmap = (HBITMAP)dcSource.SelectObject(hBitmap);

    
// Create a compatible bitmap that matches the width and height of your Gdiplus Bitmap 
    bitmap.CreateCompatibleBitmap(&dcClient, gdipBitmap->GetWidth(), gdipBitmap->GetHeight());

    
// Select this bitmap into the destination DC 
    CBitmap* pOldBitmap = dcDest.SelectObject(&bitmap);

    
// Copy the source bitmap to the destination bitmap 
    ::BitBlt(dcDest, 00, gdipBitmap->GetWidth(), gdipBitmap->GetHeight(), dcSource, 00, SRCCOPY); 

    
// Always select your objects out of their respective DC's 
    dcSource.SelectObject(oldhBitmap);
    dcDest.SelectObject(pOldBitmap);

    VERIFY(::OpenClipboard(m_hWnd));
    
// Empty it out 
    VERIFY(::EmptyClipboard()); 

    
// Place your global HBITMAP handle on the clipboard 
    VERIFY(::SetClipboardData(CF_BITMAP, bitmap.m_hObject)); 

    
// Always close the clipboard 
    ::CloseClipboard(); 

    
// We don't need the Gdiplus HBITMAP anymore 
    ::DeleteObject((HGDIOBJ)hBitmap); 

    
return TRUE;
}

 
总结
由于时间关系,只是拿出一些代码片断来说明问题,还有很多细节都在程序里,比如GDI+初始化和结束的小tool class。如果你用在项目里请改进其设计,比如选择截图区域时的刷新,以获得更好的效率。

 

 
注意到new前面的::,这表示用全局的new运算符,这是因为GDI+重新定义了new操作,而这个东西在Bitmap类身上做的事情实在不怎么样,你可以把::去掉尝试编译一下。最后别忘了delete。目前我还不确定这样new Bitmap会不会造成内存泄漏,没有正规的资料给出一些信息。谁知道的话希望告诉我一下,小弟不胜感激。
剪切选择区域可以用下面的代码
Bitmap *  forClip  =  screenBmp -> Clone(calculateRect( startPoint, overPoint), PixelFormat32bppARGB);
        PasteBitmapToClipboard(forClip);
        delete forClip;
 
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 20
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值