答:在VC中进行屏幕截图可以分成几步:
(1) 获得整个屏幕的DC,并创建一个与之兼容的DC。
CDC *pDesktopDC=GetDesktopWindow()->GetDC();
CDC memDC;
memDC.CreateCompatibleDC(pDesktopDC);
(2)创建一个与屏幕大小相同的兼容位图。
CRect RectDesktop;
GetDesktopWindow()->GetClientRect(&RectDesktop);
CBitmap bmpdesktop;
bmpdesktop.CreateCompatibleBitmap(pDesktopDC,RectDesktop.Width(),RectDesktop.Height());
(3)把位图显示在客户区。
CRect rect;
GetClientRect(&rect);
CDC *pDC=this->GetDC();
pDC->StretchBlt(0,0,rect.Width(),rect.Height(),pDesktopDC,0,0,RectDesktop.Width(),RectDesktop.Height(),SRCCOPY);
这样就能完成了简单截图的功能。
猜想:(1)按理来说呢,要想截取某个程序的图像,只要获得该程序的DC,并将之复制一份即可。
(2)那么如果截取屏幕的某一区域的话,只需将区域范围保存下来,然后对复制的位图进行裁剪即可。
问题二:看下面的截图,大家是不是看着很别扭呢?这是使用上面的程序多次截图造成的。咋一看很吃惊,这不是我想要的结果,但事实确实如此。仔细想想也就能明白了。
那么怎么样才能不出现这种结果呢?我想应该是先清空客户区的图像,然后再截图。也就是在截图开始前添加清空客户区图像的功能。但是如果仅仅是要将背景图去掉的话,那也可以用背景色去填充整个客户区呀。
CRect rc;
GetClientRect(&rc);
pDC->Rectangle(&rc);
这样确实可以避免出现上面的窗口很多的情况。但是新问题又来了,这样的话每次截图都会闪屏。看来这个方法是行不通的,不能投机取巧啊!
前面讲了屏幕截图的一点知识,其实是很好懂的。如果我想要截取屏幕的部分区域怎么办呢?
下面就来讲一下。
要保证截图的时候所有窗口都是无效的,这样在鼠标滑动的时候才不容易丢失焦点。可是这样办得到吗?至少我现在认为是不可能的,也许真的有某种方式可以做到。既然这样想行不通,那就换一个思路吧。我们可以采用欺骗的手段,先新建一个对话框,然后让它充满整个屏幕并且始终保持在所有窗口的最前面,然后将整个屏幕截取出来使之画满整个对话框窗口,然后记录鼠标在此图像内的截图区域,再传递给主窗口就可以了。这是我在网上找到的一个比较容易懂的方法。
具体实现如下:
(1)新建一个对话框,设置属性为无标题,无边框,新建类CCutScreenAreaDlg,在初始化函数中将其设置为所有窗口最前。
int cxScreen,cyScreen;
cxScreen=GetSystemMetrics(SM_CXSCREEN);
cyScreen=GetSystemMetrics(SM_CYSCREEN);
SetWindowPos(&wndTopMost,0,0,cxScreen,cyScreen,SWP_SHOWWINDOW);
(2)然后重载其WM_ERASEBKGND消息。
BOOL CCutScreenAreaDlg::OnEraseBkgnd(CDC* pDC)
{
// TODO: Add your message handler code here and/or call default
CDC *pDesktopDC=GetDesktopWindow()->GetDC();
CDC memDC;
memDC.CreateCompatibleDC(pDesktopDC);
CRect RectDesktop;
GetDesktopWindow()->GetClientRect(&RectDesktop);
CBitmap bmpdesktop;
bmpdesktop.CreateCompatibleBitmap(pDesktopDC,RectDesktop.Width(),RectDesktop.Height());
CRect rect;
GetClientRect(&rect);
pDC->StretchBlt(0,0,rect.Width(),rect.Height(),pDesktopDC,0,0,RectDesktop.Width(),RectDesktop.Height(),SRCCOPY);
return TRUE;
}
(3)记录鼠标选中的区域,即左键按下为起点,右键按下为中点。
void CCutScreenAreaDlg::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
StartPoint = point;
CDialog::OnLButtonDown(nFlags, point);
}
void CCutScreenAreaDlg::OnLButtonUp(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
EndPoint = point;
CDialog::OnLButtonUp(nFlags, point);
}
(4)在父窗口内截取图像,用到函数CutScreenArea(CRect Rect)。
void CMyView::CutScreenArea(CRect Rect)
{
CDC *pDC=GetDC();
CRect rc;
CBrush brush;
brush.CreateSolidBrush(RGB(255,255,255));
GetClientRect(&rc);
pDC->FillRect(&rc,&brush);
CDC *pDesktopDC=GetDesktopWindow()->GetDC();
CRect RectDesktop;
GetDesktopWindow()->GetClientRect(&RectDesktop);
CDC memDC;
memDC.CreateCompatibleDC(pDesktopDC);
CBitmap bmpdesktop;
bmpdesktop.CreateCompatibleBitmap(pDesktopDC,RectDesktop.Width(),RectDesktop.Height());
CRect rect;
GetClientRect(&rect);
pDC->StretchBlt(0,0,rect.Width(),rect.Height(),pDesktopDC,Rect.left,
Rect.top,Rect.Width(),Rect.Height(),SRCCOPY);
grect = Rect;
}
(5)添加一个按钮消息响应函数OnCutscreenarea() 。
void CMyView::OnCutscreenarea()
{
// TODO: Add your command handler code here
HCURSOR hCursor=AfxGetApp()->LoadCursor(IDC_CURSOR1);
SetCursor(hCursor);
CCutScreenAreaDlg dlg;
if(IDOK == dlg.DoModal())
{
StartPoint = dlg.StartPoint;
EndPoint = dlg.EndPoint;
CRect rect;
rect.left = StartPoint.x;
rect.right = EndPoint.x;
rect.bottom = EndPoint.y;
rect.top = StartPoint.y;
grect = rect;
CutScreenArea(grect);
}
}
经过以上步骤就能在客户区内显示截取的区域图像了。
下一次讲怎么保存图像。
我自己的技术还没到家,所以只讲怎么保存成bmp格式的位图文件。
一开始的时候我陷在整个屏幕截图的泥潭中,保存的图像总有好大一部分黑边。后来终于想明白了。你想截取哪个窗口的图像,就找到那个窗口的DC (设备上下文),然后创建与之兼容的DC,和与之大小的兼容位图,将DC复制到兼容的DC中,保存起来。陷在我想保存客户区内的图像,那我就找到客户区的DC,然后将之整个的保存起来。
详细代码如下:
////////////////////////////////////////////////////////////////////////////////////////
//拷贝客户区的DC内容到兼容DC
///////////////////////////////////////////////////////////////////////////////////////
CDC *pDC=GetDC();
CRect rect;
GetClientRect(&rect);
CDC bmemDC;
bmemDC.CreateCompatibleDC(pDC);
CBitmap bmprect;
bmprect.CreateCompatibleBitmap(pDC,rect.Width(),rect.Height());
bmemDC.SelectObject(&bmprect);
bmemDC.BitBlt(0,0,mbitmap.bmWidth,mbitmap.bmHeight,pDC,0,0,SRCCOPY);
/////////////////////////////////////////////////////////////////////////////////////////
//将位图信息填充到一个缓冲区内
////////////////////////////////////////////////////////////////////////////////////////
BITMAP mbitmap;
bmprect.GetBitmap(&mbitmap);
BITMAPINFOHEADER bih = {0};//位图信息头
bih.biBitCount = mbitmap.bmBitsPixel;//每个像素字节大小
bih.biCompression = BI_RGB;
bih.biHeight = mbitmap.bmHeight;//高度
bih.biPlanes = 1;
bih.biSize = sizeof(BITMAPINFOHEADER);
bih.biSizeImage = mbitmap.bmWidthBytes * mbitmap.bmHeight;//图像数据大小
bih.biWidth = mbitmap.bmWidth;//宽度
BITMAPFILEHEADER bfh = {0};//位图文件头
bfh.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);//到位图数据的偏移量
bfh.bfSize = bfh.bfOffBits + mbitmap.bmWidthBytes * mbitmap.bmHeight;//文件总的大小
bfh.bfType = (WORD)0x4d42;
byte * p = new byte[mbitmap.bmWidthBytes * mbitmap.bmHeight];//申请内存保存位图数据
GetDIBits(bmemDC.m_hDC, (HBITMAP) bmprect.m_hObject, 0, rect.Height(), p,
(LPBITMAPINFO) &bih, DIB_RGB_COLORS);//获取位图数据
///////////////////////////////////////////////////////////////////////////////////////////
//文件保存对话框,保存文件
//////////////////////////////////////////////////////////////////////////////////////////////
CString strFileName,strszFilter;
strszFilter="位图文件(*.bmp)|*.bmp|全部文件(*.*)|*.*||";
CFileDialog bmpdlg(FALSE,NULL,NULL,OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,strszFilter,NULL);
if(IDOK == bmpdlg.DoModal())
{
strFileName = bmpdlg.GetFileName();
strFileName +=".bmp";
char filename[MAX_PATH];
strcpy(filename,strFileName);
FILE *fp = fopen(filename, "w+b");
fwrite(&bfh, 1, sizeof(BITMAPFILEHEADER), fp);//写入位图文件头
fwrite(&bih, 1, sizeof(BITMAPINFOHEADER), fp);//写入位图信息头
fwrite(p, 1, mbitmap.bmWidthBytes * mbitmap.bmHeight, fp);//写入位图数据
fclose(fp);
}
delete [] p;
转自lvan GoGo 的世界