开发环境:

系统:Win7 64bits;

编译器:Visual Studio 2008 + SP1;



我们先来说说截图的原理;截图无非就是获取屏幕某个时间点的快照,然后再对此快照进行各种我们自己想要的操作,以达到我们自己想要的效果

以下为获取屏幕快照的相关代码:

//拷贝屏幕到位图中
HBITMAP CCaptureScreenDemoDlg::CopyScreenToBitmap(LPRECT lpRect,BOOL bSave)
//lpRect 代表选定区域
{
	HDC       hScrDC, hMemDC;      
	// 屏幕和内存设备描述表
	HBITMAP    hBitmap, hOldBitmap;   
	// 位图句柄
	int       nX, nY, nX2, nY2;      
	// 选定区域坐标
	int       nWidth, nHeight;
	
	// 确保选定区域不为空矩形
	if (IsRectEmpty(lpRect))
		return NULL;
	//为屏幕创建设备描述表
	hScrDC = CreateDC(L"DISPLAY", NULL, NULL, NULL);

	//为屏幕设备描述表创建兼容的内存设备描述表
	hMemDC = CreateCompatibleDC(hScrDC);
	// 获得选定区域坐标
	nX = lpRect->left;
	nY = lpRect->top;
	nX2 = lpRect->right;
	nY2 = lpRect->bottom;

	//确保选定区域是可见的
	if (nX < 0)
		nX = 0;
	if (nY < 0)
		nY = 0;
	if (nX2 > GetSystemMetrics(SM_CXSCREEN))
		nX2 = GetSystemMetrics(SM_CXSCREEN);
	if (nY2 > GetSystemMetrics(SM_CYSCREEN))
		nY2 = GetSystemMetrics(SM_CYSCREEN);
	nWidth = nX2 - nX;
	nHeight = nY2 - nY;
	// 创建一个与屏幕设备描述表兼容的位图
	hBitmap = CreateCompatibleBitmap(hScrDC, nWidth, nHeight);
	// 把新位图选到内存设备描述表中
	hOldBitmap = (HBITMAP)SelectObject(hMemDC, hBitmap);
	// 把屏幕设备描述表拷贝到内存设备描述表中
	if(bSave)
	{
		CDC dcCompatible;
		dcCompatible.CreateCompatibleDC(CDC::FromHandle(hMemDC));
		dcCompatible.SelectObject(m_pBitmap);
        
		BitBlt(hMemDC, 0, 0, nWidth, nHeight,
			dcCompatible, nX, nY, SRCCOPY);
	}
	else
	{
		BitBlt(hMemDC, 0, 0, nWidth, nHeight,
			hScrDC, nX, nY, SRCCOPY);
	}

	hBitmap = (HBITMAP)SelectObject(hMemDC, hOldBitmap);
	
	
	//得到屏幕位图的句柄
	//*************
	CDC *pMemDc = CDC::FromHandle(hMemDC);

		//m_pngMask.DrawImage(pMemDc,0,0,m_xScreen,m_yScreen,0,0,8,8);
		int x=nX;
		int y=nY;
		int w=nWidth;
		int h=nHeight;

	//清除 
	DeleteDC(hScrDC);
	DeleteDC(hMemDC);
	// 返回位图句柄
	if(bSave)
	{				
		if (OpenClipboard()) 
		{
        //清空剪贴板
        EmptyClipboard();
        //把屏幕内容粘贴到剪贴板上,
        //hBitmap 为刚才的屏幕位图句柄
        SetClipboardData(CF_BITMAP, hBitmap);
        //关闭剪贴板
        CloseClipboard();
      }
	}
	return hBitmap;
}



获取到了屏幕某一个时间点的截图之后我们就要对其进行展示,用一个窗口展示此位图,另外既然是截图我们就不需要用户再进行其他的操作,所以我们应该将用来展示截取位图的窗口设置为最顶层窗口并且为全屏;可以用Windows系统提供的API函数进行设置

BOOL SetWindowPos(  HWND hWnd,             // handle to window
  HWND hWndInsertAfter,  // placement-order handle
  int X,                 // horizontal position
  int Y,                 // vertical position  int cx,                // width
  int cy,                // height
  UINT uFlags            // window-positioning options);

将参数2设置为HWND_TOPMOST即可,以下为我自己的处理方式:

m_cx = GetSystemMetrics(SM_CXSCREEN);
	m_cy = GetSystemMetrics(SM_CYSCREEN);

	::SetWindowPos(this->GetSafeHwnd(), 
		HWND_TOPMOST, 0,0, m_cx, m_cy, SWP_NOCOPYBITS);



既然获取到屏幕快照了,最顶层窗口也设置了,那么接下来当然就是图片的展示了,图片的展示仅仅是将获取到的位图图片画到我们展示的窗口上了,具体的画法,这个仁者见仁智者见智,你喜欢用什么方法就用什么方法,我自己使用的GDIPlus的画法,具体的操作是在窗口的OnPain中。具体代码如下:

void CBackGroundDlg::OnPaint()
{
	CPaintDC dc(this); // device context for painting
	// TODO: 在此处添加消息处理程序代码
	// 不为绘图消息调用 CDialog::OnPaint()
	HDC hDC = GetDC()->GetSafeHdc();
	BITMAP bmp;
	m_pBitmap->GetBitmap(&bmp);

	CDC dcCompatible;
	dcCompatible.CreateCompatibleDC(GetDC());
	dcCompatible.SelectObject(m_pBitmap);

	CRect rect;
	GetClientRect(&rect);
	BitBlt(hDC,0,0,rect.Width(),rect.Height(),dcCompatible.GetSafeHdc(),0,0,SRCCOPY);
}

这样我们截取的图片已经展示出来了;但是在看QQ截图的时间,发现他们展示的是一张灰度图,这个其实就是在现有的图层上在画一层,当然灰色的图片是要具有透明效果的

具体代码如下:

CRect rect;
	GetClientRect(&rect);
	Graphics graph(GetDC()->GetSafeHdc());

	RectF rcDrawRect;
	rcDrawRect.X = 0;
	rcDrawRect.Y = 0;
	rcDrawRect.Width = (REAL)rect.Width();
	rcDrawRect.Height = (REAL)rect.Height();

	graph.DrawImage(m_pImgMask, rcDrawRect, 0,0, 2, 2, UnitPixel);

同样是在OnPaint中画的。

此处的m_pImgMask就是透明效果的灰色图片

Image* m_pImgMask;

m_pImgMask = Gdiplus::Image::FromFile(L"SC_MASK.png");//加载图片,我此处是用相对路径加载

这样QQ初始的界面一样了


接下来就是自由的选取截图区域:

关于自由选取截图区域我的实现方法是:

刨去选中区域,在未选中区域画灰色的遮挡图也就是上文的m_pImgMask图片

当初设想了两种方式:

1、分区域绘制

2、使用RGN绘制

分区域绘制:就是将窗口的客户区以选中区域分为上、下、左、右四个区域

使用RGN绘制:当初的设想是将整个作为一个RGN将选中的区域作为一个RGN,然后两个RGN进行补集操作获取到不属于选中区域的RGN然后在此RGN上绘制Mask图片


我自己在使用过程中使用了分区域绘制的方法,对于第二种方法,有兴趣的同学可以自己实现一下;

既然是分区域绘制那我们就要得到相关的矩形大小:

首先我们应该先根据鼠标的按下和鼠标移动的位置计算选中区域的大小

相关代码如下:实现在OnMouseMove中

CRect rcArea;
int nXDown = m_ptDown.x;
int nYDown = m_ptDown.y;
int nXCur = point.x;
int nYCur = point.y;
if (nXDown <= nXCur && nYDown <= nYCur)
{
	rcArea.left = m_ptDown.x;
	rcArea.top = m_ptDown.y;
	rcArea.right = point.x;
	rcArea.bottom = point.y;
}
else if (nXDown <= nXCur && nYDown >= nYCur)
{
	rcArea.left = m_ptDown.x;
	rcArea.top = point.y;
	rcArea.right = point.x;
	rcArea.bottom = m_ptDown.y;
}
else if (nXCur <= nXDown && nYCur >= nYDown)
{
	rcArea.left = point.x;
	rcArea.top = m_ptDown.y;
	rcArea.right = m_ptDown.x;
	rcArea.bottom = point.y;
}
else
{
	rcArea.left = point.x;
	rcArea.top = point.y;
	rcArea.right = m_ptDown.x;
	rcArea.bottom = m_ptDown.y;
}

m_ptDown为鼠标按下的位置;

计算上下左右四个矩形的相关代码如下:

void CBackGroundDlg::CalcGrayAreaRect(CRect rcArea,
									 RectF& rcLeft, 
									 RectF& rcTop, 
									 RectF& rcRight, 
									 RectF& rcBottom)
{
	CRect rect;
	GetClientRect(&rect);

	//左边
	rcLeft.X = 0;
	rcLeft.Y = 0;
	rcLeft.Width = (REAL)rcArea.left;
	rcLeft.Height = (REAL)rect.Height();


	//右边
	rcRight.X = (REAL)rcArea.right;
	rcRight.Y = 0;
	rcRight.Width = (REAL)rect.right - rcArea.right;
	rcRight.Height = (REAL)rect.Height();


	//上边
	rcTop.Y = 0;
	rcTop.X = (REAL)rcArea.left;
	rcTop.Width = (REAL)rcArea.Width();
	rcTop.Height = (REAL)rcArea.top;

	//下边
	rcBottom.X = (REAL)rcArea.left;
	rcBottom.Y = (REAL)rcArea.bottom;
	rcBottom.Width = (REAL)rcArea.Width();
	rcBottom.Height = (REAL)rect.bottom - rcArea.bottom;
}

位置获取到了就剩下绘制了,在绘制时间,现在整体的客户区绘制获取到的屏幕的位图,再在四个区域绘制灰色Mask图


再看QQ的截图,他们的截图还支持整体的移动以及8个方向(上、下、左、右、左上、左下、右上、右下)的拉伸跟缩小;

我们可以在这8个方向画8个点的图片:画法类似于画Mask的方法,位置可以自行计算获得

还有就是鼠标移动到相应区块之中时鼠标光标的变换,此处可以用自定义的鼠标也可以使用系统默认的鼠标光标

系统默认光标定义:

IDC_SIZEALL 整体移动

IDC_SIZENESW 右上角

IDC_SIZENWSE 右下角

IDC_SIZENS 上边

IDC_SIZENS 下边

IDC_SIZEWE 左边

IDC_SIZEWE 右边

IDC_SZIENWSE 左上

IDC_SIZENESW 左下

可以使用PtInRect函数判断鼠标位置是否在相应的区域,然后再设置相应区域的鼠标光标


同理可以在OnLButtonDown中判断点下的位置是否在相应的区域,然后进行相关操作

比如点击在左上角的拉伸点内,然后可以在OnMouseMove中进行左上角拉伸点内的拉伸缩小操作:

我自己的相关代码如下:

void CBackGroundDlg::TrackLeftTop(CPoint point)
{
	//TODO:左上角拉伸
	CRect rect;
	GetClientRect(&rect);

	HDC hDC = GetDC()->GetSafeHdc();
	HBITMAP hMemBitMap = 
		CreateCompatibleBitmap(hDC, rect.Width(), rect.Height());
	CDC dcCompatible;
	dcCompatible.CreateCompatibleDC(GetDC());
	dcCompatible.SelectObject(hMemBitMap);
	Graphics graph(dcCompatible.GetSafeHdc());

	CRect rcArea;
	if (point.x <= m_rcSelected.right)
	{
		rcArea.left = point.x;
		rcArea.right = m_rcSelected.right;
	}
	else if (point.x >= m_rcSelected.right)
	{
		rcArea.left = m_rcSelected.right;
		rcArea.right = point.x;
	}

	if (point.y >= m_rcSelected.bottom)
	{
		rcArea.top = m_rcSelected.bottom;
		rcArea.bottom = point.y;
	}
	else if (point.y <= m_rcSelected.bottom)
	{
		rcArea.top = point.y;
		rcArea.bottom = m_rcSelected.bottom;
	}

	m_rcRemote = rcArea;
	HBITMAP hBmp = (HBITMAP)m_pBitmap->GetSafeHandle();
	Bitmap bmp(hBmp, NULL);
	graph.DrawImage(&bmp, 0,0,rect.Width(), rect.Height());

	//画灰色的四个区域
	RectF rcLeft, rcRight, rcTop, rcBottom;
	CalcGrayAreaRect(rcArea, rcLeft, rcTop, rcRight, rcBottom);
	graph.DrawImage(m_pImgMask, rcLeft, 0,0, 1, 1, UnitPixel);
	graph.DrawImage(m_pImgMask, rcRight, 0,0, 1, 1, UnitPixel);
	graph.DrawImage(m_pImgMask, rcTop, 0,0, 1, 1, UnitPixel);
	graph.DrawImage(m_pImgMask, rcBottom, 0,0, 1, 1, UnitPixel);

	//画8个拉伸的点
	//top left
	graph.DrawImage(m_pImgDot, rcArea.left - 3, rcArea.top - 3, 5, 5);
	//bottom left
	graph.DrawImage(m_pImgDot, rcArea.left - 3, rcArea.bottom - 2, 5, 5);
	//top right
	graph.DrawImage(m_pImgDot, rcArea.right - 2, rcArea.top - 3, 5, 5);
	//bottom right
	graph.DrawImage(m_pImgDot, rcArea.right - 2, rcArea.bottom - 2, 5, 5);
	//left
	graph.DrawImage(m_pImgDot, rcArea.left -3, rcArea.bottom - (rcArea.bottom - rcArea.top)/2, 5, 5);
	//right
	graph.DrawImage(m_pImgDot, rcArea.right - 2, rcArea.bottom - (rcArea.bottom - rcArea.top)/2, 5, 5);
	//top 
	graph.DrawImage(m_pImgDot, rcArea.right - (rcArea.right - rcArea.left)/2, rcArea.top - 3, 5, 5);
	//bottom
	graph.DrawImage(m_pImgDot, rcArea.right - (rcArea.right - rcArea.left)/2, rcArea.bottom - 2, 5, 5);

	//画一个线边框
	Pen hPen(Color(255, 100, 100, 100), 1);
	graph.DrawLine(&hPen, rcArea.left, rcArea.top, rcArea.right, rcArea.top);
	graph.DrawLine(&hPen, rcArea.right, rcArea.top, rcArea.right, rcArea.bottom);
	graph.DrawLine(&hPen, rcArea.left, rcArea.bottom, rcArea.right, rcArea.bottom);
	graph.DrawLine(&hPen, rcArea.left, rcArea.top, rcArea.left, rcArea.bottom);

	RectF rcTitleSize;
	if (rcArea.top > 20)
	{

		rcTitleSize.X = (REAL)rcArea.left;
		rcTitleSize.Y = (REAL)rcArea.top - 20;
		rcTitleSize.Width = 245;
		rcTitleSize.Height = 20;
	}
	else
	{
		rcTitleSize.X = (REAL)rcArea.left;
		rcTitleSize.Y = (REAL)rcArea.top;
		rcTitleSize.Width = 245;
		rcTitleSize.Height = 20;
	}
	graph.DrawImage(m_pImgMask, rcTitleSize, 0,0, 1, 1, UnitPixel);

	CString csTitle;
	csTitle.Format(L"起始位置:%d.%d 矩形大小 %d*%d", 
		rcArea.left, rcArea.top,
		rcArea.right - rcArea.left,
		rcArea.bottom - rcArea.top);

	FontFamily fontFamily(L"楷体");
	Gdiplus::Font font(&fontFamily, 14, FontStyleRegular, UnitPixel);
	StringFormat strFormat;
	strFormat.SetAlignment(StringAlignmentCenter);
	strFormat.SetLineAlignment(StringAlignmentCenter);
	graph.DrawString(csTitle, -1, 
		&font, 
		PointF(rcTitleSize.X, rcTitleSize.Y+2),
		&SolidBrush(Color(255, 255,255,255)));

	RectF rcOperateBar, rcFinish, rcSave, rcClipboard, rcArrow, rcEllipse, rcRect;
	CalcOperateBarRect(rcArea, rcOperateBar, rcFinish, rcSave, rcClipboard, rcArrow, rcEllipse, rcRect);
	graph.DrawImage(m_pimgOperateBG, rcOperateBar, 0,0, 1, 1, UnitPixel);
	graph.DrawImage(m_pImgFinish, rcFinish, 0,0, 30, 30, UnitPixel);
	graph.DrawImage(m_pImgSave, rcSave, 0,0, 30, 30, UnitPixel);
	graph.DrawImage(m_pImgClipboard, rcClipboard, 0,0, 30, 30, UnitPixel);
	graph.DrawImage(m_pImgArrow, rcArrow, 0,0, 30, 30, UnitPixel);
	graph.DrawImage(m_pImgEllipse, rcEllipse, 0,0, 30, 30, UnitPixel);
	graph.DrawImage(m_pImgRectangle, rcRect, 0,0, 30, 30, UnitPixel);

	BitBlt(hDC,0,0,rect.Width(),rect.Height(),dcCompatible.GetSafeHdc(),0,0,SRCCOPY);
}

其余7个点的处理类似于此




接下来就是进行相关涂鸦操作的处理了

代码中已实现,有兴趣的同学可以自行查看代码。