为了自己做出一个远控程序,自己花了点时间学习了有关绘图方面的知识点。
一.基本概念的了解
为了实现实时屏幕显示的功能,首先学习了下《windows程序设计》的第14章,位图和位块的传输。
理解了下位图和位块的区别,基本概念。
概念一:位图与图元的区别:
位图 图元
点阵 矢量 (位图与图元的主要区别就在这里了)
容易失真 不失真
较大 较小
传输速度快 传输速度慢
概念二:与设备无关的位图
DDB 设备相关位图
DIB 设备无关位图
windows编程里面提到,这两个概念有时候是很难区分的。所以我在这里就不具体讲解两者的区别了,对我们的功能实现应该没什么关系。但是因为这是个经常出现的字眼,所以这里先提一下。
二.BitBlt函数
接下来我们来看BitBlt 函数,这个函数是我们这次功能实现的最关键的一个函数了~~~~
BOOL BitBlt( __in HDC hdcDest, __in int nXDest, __in int nYDest, __in int nWidth, __in int nHeight, __in HDC hdcSrc, __in int nXSrc, __in int nYSrc, __in DWORD dwRop );参数虽然多,但是用起来不难
hdcDest 目标设备描述表
nXDest 目标起始x坐标位置
nYDest 目标起始y坐标位置
nWidth 宽度
nHeight 高度
hdcSrc 源设备描述表
nXSrc 源起始x坐标
nYSrc 源起始y坐标
dwRop 这个参数比较复杂,我们这里就是简单的原封不动的复制,所以使用了 SRCCOPY
更多关于这个函数的用法,参照MSDN
当然了,如果你想缩放图片,你还可以使用函数StretchBlt 这个函数比BitBlt多了两个参数。
在我的程序中,将屏幕的图像传输到我自己的程序中:
hdcClient = BeginPaint (hwnd, &ps); //获得目标(本程序)设备描述符
hdcWindow = GetWindowDC (NULL); //获得源目标(屏幕)设备描述符
BitBlt (hdcClient, 0, 0, cxClient, cyClient, hdcWindow , 0, 0, SRCCOPY);
三.实时显示
我通过设置定时器,每隔1ms刷新一次软件的客户区。当然发消息没有这么快。但是至少这样能保证已经达到了最快的更新速度。
程序开始时触发定时器
case WM_CREATE:
SetTimer (hwnd, ID_TIMER, 100, TimerProc) ;
return 0 ;
定时器函数
VOID CALLBACK TimerProc (HWND hwnd, UINT message, UINT iTimerID, DWORD dwTime)
{
InvalidateRect(hwnd, NULL, NULL); //无效整个屏幕客户区,重绘客户区
}
当这个功能实现的时候,出现了一个很奇怪的问题~~~。给大家截个图看看当前效果:
有很多镜像!~~~这样的结果是正确的。我如果把我的程序放到我的另一个显示器上面(我有两个显示屏),那么程序显示就正常了,显示的是我原来那个屏幕上的图像。
四:保存bmp图片
这里直接贴代码了:
HBITMAP ScreenCapture(LPWSTR filename ,WORD BitCount,LPRECT lpRect)
{
HBITMAP hBitmap;
// 显示器屏幕DC
HDC hScreenDC = CreateDC(L"DISPLAY", NULL, NULL, NULL);
HDC hmemDC = CreateCompatibleDC(hScreenDC);
// 显示器屏幕的宽和高
int ScreenWidth = GetDeviceCaps(hScreenDC, HORZRES);
int ScreenHeight = GetDeviceCaps(hScreenDC, VERTRES);
// 旧的BITMAP,用于与所需截取的位置交换
HBITMAP hOldBM;
// 保存位图数据
PVOID lpvpxldata;
// 截屏获取的长宽及起点
INT ixStart;
INT iyStart;
INT iX;
INT iY;
// 位图数据大小
DWORD dwBitmapArraySize;
// 几个大小
DWORD nBitsOffset;
DWORD lImageSize ;
DWORD lFileSize ;
// 位图信息头
BITMAPINFO bmInfo;
// 位图文件头
BITMAPFILEHEADER bmFileHeader;
// 写文件用
HANDLE hbmfile;
DWORD dwWritten;
// 如果LPRECT 为NULL 截取整个屏幕
ixStart = iyStart = 0;
iX = ScreenWidth;
iY = ScreenHeight;
// 创建BTIMAP
hBitmap = CreateCompatibleBitmap(hScreenDC, iX, iY);
// 将BITMAP选择入内存DC。
hOldBM = (HBITMAP)SelectObject(hmemDC, hBitmap);
// BitBlt屏幕DC到内存DC,根据所需截取的获取设置参数
BitBlt(hmemDC, 0, 0, iX, iY, hScreenDC, ixStart, iyStart, SRCCOPY);
// 将旧的BITMAP对象选择回内存DC,返回值为被替换的对象,既所截取的位图
hBitmap = (HBITMAP)SelectObject(hmemDC, hOldBM);
if(filename == NULL)
{
DeleteDC( hScreenDC);
DeleteDC(hmemDC);
return hBitmap;
}
// 为位图数据申请内存空间
dwBitmapArraySize = ((((iX*32) + 31) & ~31)>> 3)* iY;
lpvpxldata = HeapAlloc(GetProcessHeap(),HEAP_NO_SERIALIZE,dwBitmapArraySize);
ZeroMemory(lpvpxldata,dwBitmapArraySize);
// 添充 BITMAPINFO 结构
ZeroMemory(&bmInfo,sizeof(BITMAPINFO));
bmInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmInfo.bmiHeader.biWidth = iX;
bmInfo.bmiHeader.biHeight = iY;
bmInfo.bmiHeader.biPlanes = 1;
bmInfo.bmiHeader.biBitCount = BitCount;
bmInfo.bmiHeader.biCompression = BI_RGB;
// 添充 BITMAPFILEHEADER 结构
ZeroMemory(&bmFileHeader,sizeof(BITMAPFILEHEADER));
nBitsOffset = sizeof(BITMAPFILEHEADER) + bmInfo.bmiHeader.biSize;
lImageSize =
((((bmInfo.bmiHeader.biWidth * bmInfo.bmiHeader.biBitCount) + 31) & ~31)>> 3)
* bmInfo.bmiHeader.biHeight;
lFileSize = nBitsOffset + lImageSize;
bmFileHeader.bfType = 'B'+('M'<<8);
bmFileHeader.bfSize = lFileSize;
bmFileHeader.bfOffBits = nBitsOffset;
// 获取DIB用于写入到文件
GetDIBits(hmemDC, hBitmap, 0, bmInfo.bmiHeader.biHeight,
lpvpxldata, &bmInfo, DIB_RGB_COLORS);
// 写文件
hbmfile = CreateFile(filename,
GENERIC_WRITE,
FILE_SHARE_WRITE,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL);
WriteFile(hbmfile,&bmFileHeader,sizeof(BITMAPFILEHEADER),&dwWritten,NULL);
WriteFile(hbmfile,&bmInfo,sizeof(BITMAPINFO),&dwWritten,NULL);
WriteFile(hbmfile,lpvpxldata,lImageSize,&dwWritten,NULL);
CloseHandle(hbmfile);
// 释放内存,清除不同的DC。
// 这里没有删除BITMAP对象,需在显示完成后删除
HeapFree(GetProcessHeap(),HEAP_NO_SERIALIZE,lpvpxldata);
ReleaseDC(0, hScreenDC);
DeleteDC(hmemDC);
return hBitmap;
}
这段代码基本上不加修改就能直接用了。
五.格式转换
一般远控在传输图像的时候都是把图片进行了压缩的。所以我想把bmp格式的图片转换成png。这里使用了GDI+的库。很方便就实现了。
windowsSDK程序需要的注意点:
1.保证是.cpp格式 .c格式的话就不能 因为这个库只有c++下面才能使用。
2.使用前引入头文件 和 库
#include <GdiPlus.h>
using namespace Gdiplus;
#pragma comment(lib, "gdiplus.lib")
3.实例代码
参考下面这篇文章,将的非常好:http://blog.csdn.net/yuzl32/article/details/5389919
#include <windows.h>
#include <gdiplus.h>
#include <stdio.h>
using namespace Gdiplus;
#pragma comment(lib,"gdiplus")
int GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
{
UINT num = 0; // number of image encoders
UINT size = 0; // size of the image encoder array in bytes
ImageCodecInfo* pImageCodecInfo = NULL;
//2.获取GDI+支持的图像格式编码器种类数以及ImageCodecInfo数组的存放大小
GetImageEncodersSize(&num, &size);
if(size == 0)
return -1; // Failure
//3.为ImageCodecInfo数组分配足额空间
pImageCodecInfo = (ImageCodecInfo*)(malloc(size));
if(pImageCodecInfo == NULL)
return -1; // Failure
//4.获取所有的图像编码器信息
GetImageEncoders(num, size, pImageCodecInfo);
//5.查找符合的图像编码器的Clsid
for(UINT j = 0; j < num; ++j)
{
if( wcscmp(pImageCodecInfo[j].MimeType, format) == 0 )
{
*pClsid = pImageCodecInfo[j].Clsid;
free(pImageCodecInfo);
return j; // Success
}
}
//6.释放步骤3分配的内存
free(pImageCodecInfo);
return -1; // Failure
}
INT main()
{
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
//1.初始化GDI+,以便后续的GDI+函数可以成功调用
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
CLSID encoderClsid;
Status stat;
//7.创建Image对象并加载图片
Image* image = new Image(L"f://11.bmp");
// Get the CLSID of the PNG encoder.
GetEncoderClsid(L"image/png", &encoderClsid);
//8.调用Image.Save方法进行图片格式转换,并把步骤3)得到的图像编码器Clsid传递给它
stat = image->Save(L"11.png", &encoderClsid, NULL);
if(stat == Ok)
printf("Bird.png was saved successfully/n");
else
printf("Failure: stat = %d/n", stat);
//9.释放Image对象
delete image;
//10.清理所有GDI+资源
GdiplusShutdown(gdiplusToken);
return 0;
}
六.键盘热键实现截图。
按下空格实现截图。其实这里还可以改进,焦点必须在程序里面才能进行截图。其实可以Hook键盘消息来进行截图操作。
最后发一个不完善的版本:
/*---------------------------------------
屏幕实时监控 热键截图软件
作者:Miibotree
---------------------------------------*/
#include <windows.h>
#include <GdiPlus.h>
using namespace Gdiplus;
#pragma comment(lib, "gdiplus.lib")
#define ID_TIMER 1
HBITMAP ghBitmap = NULL;
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
VOID CALLBACK TimerProc (HWND, UINT, UINT, DWORD );
HBITMAP ScreenCapture(LPWSTR filename ,WORD BitCount,LPRECT lpRect); //全屏截图
int GetEncoderClsid(const WCHAR* format, CLSID* pClsid); // Get the CLSID of the PNG encoder.
BOOL Bmp2Png(); //进行格式转化
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName [] = TEXT ("BitBlt") ;
HWND hwnd ;
MSG msg ;
WNDCLASS wndclass ;
wndclass.style = CS_HREDRAW | CS_VREDRAW ;
wndclass.lpfnWndProc = WndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (NULL, IDI_INFORMATION) ;
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = szAppName ;
if (!RegisterClass (&wndclass))
{
MessageBox (NULL, TEXT ("This program requires Windows NT!"),
szAppName, MB_ICONERROR) ;
return 0 ;
}
hwnd = CreateWindow (szAppName, TEXT ("BitBlt Demo"),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL) ;
ShowWindow (hwnd, iCmdShow) ;
UpdateWindow (hwnd) ;
while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
return msg.wParam ;
}
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static int cxClient, cyClient, cxSource, cySource ;
HDC hdcClient, hdcWindow ;
int x, y ;
PAINTSTRUCT ps ;
HDC hMemDC; // 内存设备描述表
HBITMAP hBitmap, hOldBitmap; // 位图句柄,用于替换内存中图像
RECT rect; //矩形区域
switch (message)
{
case WM_CREATE:
SetTimer (hwnd, ID_TIMER, 100, TimerProc) ;
return 0 ;
case WM_SIZE:
cxClient = LOWORD (lParam) ; //本程序窗口宽度
cyClient = HIWORD (lParam) ; //本程序窗口长度
return 0 ;
case WM_KEYDOWN:
switch(wParam)
{
case VK_SPACE:
//截图函数
ghBitmap = ScreenCapture(L"D:\\taskmgr.bmp" ,32, NULL);
//格式转换
Bmp2Png();
return 0;
}
return 0;
case WM_PAINT:
hdcClient = BeginPaint (hwnd, &ps); //获得目标(本程序)设备描述符
hdcWindow = GetWindowDC (NULL); //获得源目标(屏幕)设备描述符
BitBlt (hdcClient, 0, 0, cxClient, cyClient, hdcWindow , 0, 0, SRCCOPY);
//目标设备 源设备
cxSource = GetSystemMetrics (SM_CXSCREEN); //获得屏幕分辨率
cySource = GetSystemMetrics (SM_CYSCREEN);
rect.left = 0; rect.right = cxSource; rect.top = 0; rect.bottom = cySource;
ReleaseDC (hwnd, hdcWindow) ;
EndPaint (hwnd, &ps) ;
return 0 ;
case WM_DESTROY:
PostQuitMessage (0) ;
return 0 ;
}
return DefWindowProc (hwnd, message, wParam, lParam) ;
}
VOID CALLBACK TimerProc (HWND hwnd, UINT message, UINT iTimerID, DWORD dwTime)
{
InvalidateRect(hwnd, NULL, NULL);
}
HBITMAP ScreenCapture(LPWSTR filename ,WORD BitCount,LPRECT lpRect)
{
HBITMAP hBitmap;
// 显示器屏幕DC
HDC hScreenDC = CreateDC(L"DISPLAY", NULL, NULL, NULL);
HDC hmemDC = CreateCompatibleDC(hScreenDC);
// 显示器屏幕的宽和高
int ScreenWidth = GetDeviceCaps(hScreenDC, HORZRES);
int ScreenHeight = GetDeviceCaps(hScreenDC, VERTRES);
// 旧的BITMAP,用于与所需截取的位置交换
HBITMAP hOldBM;
// 保存位图数据
PVOID lpvpxldata;
// 截屏获取的长宽及起点
INT ixStart;
INT iyStart;
INT iX;
INT iY;
// 位图数据大小
DWORD dwBitmapArraySize;
// 几个大小
DWORD nBitsOffset;
DWORD lImageSize ;
DWORD lFileSize ;
// 位图信息头
BITMAPINFO bmInfo;
// 位图文件头
BITMAPFILEHEADER bmFileHeader;
// 写文件用
HANDLE hbmfile;
DWORD dwWritten;
// 如果LPRECT 为NULL 截取整个屏幕
ixStart = iyStart = 0;
iX = ScreenWidth;
iY = ScreenHeight;
// 创建BTIMAP
hBitmap = CreateCompatibleBitmap(hScreenDC, iX, iY);
// 将BITMAP选择入内存DC。
hOldBM = (HBITMAP)SelectObject(hmemDC, hBitmap);
// BitBlt屏幕DC到内存DC,根据所需截取的获取设置参数
BitBlt(hmemDC, 0, 0, iX, iY, hScreenDC, ixStart, iyStart, SRCCOPY);
// 将旧的BITMAP对象选择回内存DC,返回值为被替换的对象,既所截取的位图
hBitmap = (HBITMAP)SelectObject(hmemDC, hOldBM);
if(filename == NULL)
{
DeleteDC( hScreenDC);
DeleteDC(hmemDC);
return hBitmap;
}
// 为位图数据申请内存空间
dwBitmapArraySize = ((((iX*32) + 31) & ~31)>> 3)* iY;
lpvpxldata = HeapAlloc(GetProcessHeap(),HEAP_NO_SERIALIZE,dwBitmapArraySize);
ZeroMemory(lpvpxldata,dwBitmapArraySize);
// 添充 BITMAPINFO 结构
ZeroMemory(&bmInfo,sizeof(BITMAPINFO));
bmInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmInfo.bmiHeader.biWidth = iX;
bmInfo.bmiHeader.biHeight = iY;
bmInfo.bmiHeader.biPlanes = 1;
bmInfo.bmiHeader.biBitCount = BitCount;
bmInfo.bmiHeader.biCompression = BI_RGB;
// 添充 BITMAPFILEHEADER 结构
ZeroMemory(&bmFileHeader,sizeof(BITMAPFILEHEADER));
nBitsOffset = sizeof(BITMAPFILEHEADER) + bmInfo.bmiHeader.biSize;
lImageSize =
((((bmInfo.bmiHeader.biWidth * bmInfo.bmiHeader.biBitCount) + 31) & ~31)>> 3)
* bmInfo.bmiHeader.biHeight;
lFileSize = nBitsOffset + lImageSize;
bmFileHeader.bfType = 'B'+('M'<<8);
bmFileHeader.bfSize = lFileSize;
bmFileHeader.bfOffBits = nBitsOffset;
// 获取DIB用于写入到文件
GetDIBits(hmemDC, hBitmap, 0, bmInfo.bmiHeader.biHeight,
lpvpxldata, &bmInfo, DIB_RGB_COLORS);
// 写文件
hbmfile = CreateFile(filename,
GENERIC_WRITE,
FILE_SHARE_WRITE,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL);
WriteFile(hbmfile,&bmFileHeader,sizeof(BITMAPFILEHEADER),&dwWritten,NULL);
WriteFile(hbmfile,&bmInfo,sizeof(BITMAPINFO),&dwWritten,NULL);
WriteFile(hbmfile,lpvpxldata,lImageSize,&dwWritten,NULL);
CloseHandle(hbmfile);
// 释放内存,清除不同的DC。
// 这里没有删除BITMAP对象,需在显示完成后删除
HeapFree(GetProcessHeap(),HEAP_NO_SERIALIZE,lpvpxldata);
ReleaseDC(0, hScreenDC);
DeleteDC(hmemDC);
return hBitmap;
}
int GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
{
UINT num = 0; // number of image encoders
UINT size = 0; // size of the image encoder array in bytes
ImageCodecInfo* pImageCodecInfo = NULL;
//2.获取GDI+支持的图像格式编码器种类数以及ImageCodecInfo数组的存放大小
GetImageEncodersSize(&num, &size);
if(size == 0)
return -1; // Failure
//3.为ImageCodecInfo数组分配足额空间
pImageCodecInfo = (ImageCodecInfo*)(malloc(size));
if(pImageCodecInfo == NULL)
return -1; // Failure
//4.获取所有的图像编码器信息
GetImageEncoders(num, size, pImageCodecInfo);
//5.查找符合的图像编码器的Clsid
for(UINT j = 0; j < num; ++j)
{
if( wcscmp(pImageCodecInfo[j].MimeType, format) == 0 )
{
*pClsid = pImageCodecInfo[j].Clsid;
free(pImageCodecInfo);
return j; // Success
}
}
//6.释放步骤3分配的内存
free(pImageCodecInfo);
return -1; // Failure
}
BOOL Bmp2Png()
{
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
//1.初始化GDI+,以便后续的GDI+函数可以成功调用
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
CLSID encoderClsid;
Status stat;
//7.创建Image对象并加载图片
Image* image = new Image(L"D:\\taskmgr.bmp");
// Get the CLSID of the PNG encoder.
GetEncoderClsid(L"image/png", &encoderClsid);
//8.调用Image.Save方法进行图片格式转换,并把步骤3)得到的图像编码器Clsid传递给它
stat = image->Save(L"D:\\taskmgr.png", &encoderClsid, NULL);
if(stat == Ok)
MessageBoxA(NULL, "格式转换成功", "成功", MB_OK);
else
MessageBoxA(NULL, "格式转换失败", "失败", MB_OK | MB_ICONERROR);
//9.释放Image对象
delete image;
//10.清理所有GDI+资源
GdiplusShutdown(gdiplusToken);
return TRUE;
}
七.改进之处
可以自动根据时间戳创建文件以及文件夹,加上键盘Hook功能。