最近想用VC写一个抓图程序。好久没用VC了,绝大部分知识都忘得一干二净了。 经过查阅无数资料之后终于得出一个差不多像样的程序来。 在这里说说基本思路吧。代码就不上了。

功能需求:执行抓图功能后,按下鼠标左键移动鼠标,可以在屏幕上框出一个框, 松开鼠标左键即可把选中部分的图像复制到主窗口中。然后再根据情况保存成图片。

基本思路如下:


屏幕截图可以通过BitBlt来实现。用以下方法创建一个指向整个屏幕的DC:

CDC *pScreenDC = new CDC();
HDC hScreenDC = CreateDC("DISPLAY", NULL, NULL, NULL);
pScreenDC->Attach(hScreenDC);

要使用BitBltmemDC是不可少的,因此要创建memDC

CDC *pMemDC = new CDC();
pMemDC->CreateCompatibleDC(pScreenDC);

然后创建位图:

CBitmap *pBitmap = new CBitmap();
pBitmap->CreateCompatibleBitmap(pScreenDC, pRect->Width(), pRect->Height());   # pRect为用户选取的范围
CBitmap *pOldBitmap = pMemDC->SelectObject(pBitmap);

之后就可以把指定区域的内容复制到位图中。

pMemDC->BitBlt(0, 0, pRect->Width(), pRect->Height(), pScreenDC, 0, 0, SRCCOPY);

然后做些收尾工作,将位图对象返回即可:

pBitmap = pMemDC->SelectObject(pOldBitmap);
pMemDC->DeleteDC();
delete pMemDC;
return pBitmap;

需要将位图绘制到画面上时,使用同样的方法即可,只是这次BitBlt的方向是从 pMemDCCClientDC dc(this)


在屏幕上直接画方框是不现实的,但可以换个思路:做个跟屏幕一样大的对话框, 将屏幕内容复制到这个对话框上,再在这个对话框上画方框。 这样看起来就像是在整个屏幕上画一样。

在资源编辑器中建一个对话框,不用加任何控件,并去掉标题栏。

在对话框的OnInitDialog()中首先按照上述方法将整个屏幕复制到对话框上。 屏幕大小可以用 GetSystemMetric 函数获得:

UINT nScreenWidth  = GetSystemMetrics(SM_CXSCREEN);
UINT nScreenHeight = GetSystemMetrics(SM_CYSCREEN);

然后将窗口大小设置为全屏,并设置为总在最前。这样,对话框打开时屏幕不会发生任何变化, 但实质上屏幕已不再是原来的“屏幕”,而是由对话框“画出”的东西了。


关于文档和视图的分工:文档负责处理数据,数据处理完后放在文档类的成员变量中即可, 并执行 UpdateAllViews(NULL) 来更新视图。 视图类在 OnDraw 中,通过 GetDocument() 函数获得自己的文档,取出其中的数据, 再将其绘制到画面上。


2009/3/1更新

怎样快速进行图像运算:有时需要对图像做类似于Photoshop的滤镜一样的操作,要针对每个像素进行计算。 如果此时先建立DC再通过DC进行GetPixel/SetPixel,那速度是相当慢而且极其浪费CPU。 应该这样做:

// 获取位图信息
BITMAP bitmap;
pBitmap->GetBitmap(&bitmap);  // CBitmap * pBitmap;

// 为位图数据分配内存
DWORD count = bitmap.bmWidth * bitmap.bmHeight * bitmap.bmBitsPixel / 8;
BYTE * pData = (BYTE*)malloc(sizeof(BYTE) * count);

// 获取位图数据
pBitmap->GetBitmapBits(count, pData);

上述代码只能在位图宽度是8的整数、32位图像的情况下使用,其他情况没有测试。 此时,获取到的pData就是位图数据,四个字节一组,分别是(R, G, B, 保留), 像素顺序是从左上角到右下角。

这样直接对pData进行计算即可。计算完成后,再用SetBitmapBits将图像放回位图中:

pBitmap->SetBitmapBits(count, pData);