windows编程(四)使用位图

来源:互联网 发布:mysql error code 1022 编辑:程序博客网 时间:2024/06/05 07:57


基本图形的绘制画出来的图形比较难看,即使花好看了,那也得费不少力气。至少计算是必须的,那我们为何不用现成的图形文件呢。所以就有了位图这一讲。在讲位图之前,我们先看下什么叫做绘图模式。本人看过一个前辈的游戏设计的博客。可能大家也知道,前辈的ID叫浅墨_毛星云。感兴趣的可以去搜搜。前辈的博客有一篇就是设置透明的。里面用到像素的运算。今天看到绘图模式,赶脚有点类似。


好,那我们就从绘图模式看起。

试想一下,我们为什么能够在一个黑色的背景上面绘制白色的矩形。很简单的道理,我们用白色的像素点替换了原来的黑色像素点,然后该显示黑色的地方还是显示黑色,不该显示黑色的地方已经被白色给替代掉了。替换只是一种方式,我们还可以通过其他的运算来实现得到新的像素点然后再来画上去。这个运算的过程就是光栅运算。光栅运算的运算符,比如说与或非,copy,亦或等就是光栅运算符,也叫ROP码。我们在GDI中有16种ROP码,如下图所示:



我们在黑色背景上话白色矩形,用到的就是R2_COPYPEN绘图模式。而默认的也是这种绘图模式。这是自然,不然怎么绘图。用黑色的画还显示白色的不成。那假如我们不想用这种绘图模式怎么办。ok,有面向对象编程的朋友都应该有SetXXX和GetXXX的概念,要想设置其他的绘图模式,我们用SetROP2来设置  SetROP2(hdc,R2_R2XORPEN)就可以讲绘图模式改变了。要想获得当前的绘图模式,就用GetROP2(hdc)


OK ,我们有了绘图模式的概念,那我们接下来看位图和一个用位运算设置透明背景的实例。例子也是来自浅墨前辈的。本人有幸能偶遇浅墨前辈的blog。

创建和使用位图。

先来了解一下什么叫做与设备无关的位图(DIB),我们的位图存在有两个地方,一个是内存中,一个是磁盘中,在内存中的时候,位图的的颜色深度和当前设备的是一样的。我们不需要装换。在磁盘中的位图文件理解起来就有点问题,颜色深度和当前设备往往是不一样的。那我们要显示一个磁盘上的位图,我们应该怎样做。我们有个专门描述磁盘位图颜色深度等信息的数据,然后就根据这些颜色深度等信息来转换到需要的颜色信息。我们把描述颜色信息的数据再加上位图,就叫做与设备无关的位图(DIB)。位图文件包含bitmap头和DIB数据区域

位图的定义:可以直接载入位图文件,也可以在资源中创建位图。这两者的区别在于前者在程序运行的时候从DIB中读取数据然后再进行转换。后者不用转换,在程序链接的时候就装进程序里面,所以我们会看到如果在资源中国定义位图的话,会发现生成都可执行文件相对来说要大一点。而在磁盘上读取位图文件要小一点。正是因为在资源中定义资源文件是当成可执行文件的一部分来装入的。我们PE文件格式总有个资源块。位图文件就是在这一块中。

<1>在资源中定义位图。创建过程的话,稍微了解一点vc的都知道。本人比较“怀旧“哈,在舍友们都装上高大上的vs的时候还在用vc6.0。我们要讲的不是和定义位图,要了解怎么在程序中怎么使用位图。用什么API加载位图。我们用LoadBitmap(HINSTANCE, lpBitmapName);第二个参数如果用ID的话,要用到MAKEINTRESOURCE这个宏。

<2>加载磁盘中的文件。我们用到LoadImage。当然,LoadImage也可以加载资源中的位图。可以这么说,无论是磁盘中还是资源中都可以用LoadImage来加载了。

小弟在写这里的时候,也有点小纠结,因为看的文档是第二版,现在都出到第四版典藏版了。网上查了资料页说LoadBitmap已经被LoadImage取代了  

LoadBitmap is available for use in the operating systems specified in the Requirements section. It may be altered or unavailable in subsequent versions. Instead, useLoadImage and DrawFrameControl.]所以建议伙伴们使用LoadImage函数。^_^。还有一点,在看《windows环境下32位汇编语言程序设计》中小弟也有点郁闷,罗云彬老师讲的一个读取DIB文件内容的。如有朋友知道到底是干嘛的,麻烦指教。因为好像不用这么麻烦的。

看完位图的载入,那我们要使用位图,如何使用,看下面。本人这样写也是为了保证自己思路不混乱而已。本人也是在看书编码写博客的。

使用位图离不开BitBlt函数,而BitBlt函数又是块传送操作的一个函数。那我们就得先知道什么叫块传送,什么叫块传送方式和块传送函数。块传送函数有哪些。

块传送

在块传送的前贴上我从浅墨那copy下来的一个程序。稍有修改。^_^

#include <windows.h>//全局变量声明HINSTANCE hInst;HBITMAP hbmp;HDCmdc;//全局函数的声明ATOMMyRegisterClass(HINSTANCE hInstance);BOOLInitInstance(HINSTANCE, int);LRESULT CALLBACKWndProc(HWND, UINT, WPARAM, LPARAM);voidMyPaint(HDC hdc);//****Winmain函数,程序入口点函数**************************************int APIENTRY WinMain(HINSTANCE hInstance,                     HINSTANCE hPrevInstance,                     LPSTR     lpCmdLine,                     int       nCmdShow){MSG msg;MyRegisterClass(hInstance);if (!InitInstance (hInstance, nCmdShow)) {return FALSE;}//消息循环while (GetMessage(&msg, NULL, 0, 0)) {TranslateMessage(&msg);DispatchMessage(&msg);}return msg.wParam;}//****﹚设计一个窗口类,类似填空题,使用窗口结构体*************************ATOM MyRegisterClass(HINSTANCE hInstance){WNDCLASSEX wcex;wcex.cbSize = sizeof(WNDCLASSEX); wcex.style= CS_HREDRAW | CS_VREDRAW;wcex.lpfnWndProc= (WNDPROC)WndProc;wcex.cbClsExtra= 0;wcex.cbWndExtra= 0;wcex.hInstance= hInstance;wcex.hIcon= NULL;wcex.hCursor= NULL;wcex.hCursor= LoadCursor(NULL, IDC_ARROW);wcex.hbrBackground= (HBRUSH)(COLOR_WINDOW+1);wcex.lpszMenuName= NULL;wcex.lpszClassName= "canvas";wcex.hIconSm= NULL;return RegisterClassEx(&wcex);}//****初始化函数*************************************// 1.建立与窗口DC兼容的内存DC// 2.从文件加载位图并存至内存DC中BOOL InitInstance(HINSTANCE hInstance, int nCmdShow){HWND hWnd;HDC hdc;hInst = hInstance;hWnd = CreateWindow("canvas", "绘图窗口" , WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);if (!hWnd){return FALSE;}MoveWindow(hWnd,10,10,800,600,true);ShowWindow(hWnd, nCmdShow);UpdateWindow(hWnd);hdc = GetDC(hWnd);mdc = CreateCompatibleDC(hdc);hbmp = (HBITMAP)LoadImage(NULL,"bg.bmp",IMAGE_BITMAP,800,600,LR_LOADFROMFILE); SelectObject(mdc,hbmp);MyPaint(hdc);ReleaseDC(hWnd,hdc);return TRUE;}//****自定义绘图函数*********************************void MyPaint(HDC hdc){BitBlt(hdc,0,0,800,600,mdc,0,0,SRCCOPY);//采用BitBlt函数贴图}//****消息处理函数**********************************LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam){PAINTSTRUCT ps;HDC hdc;switch (message){case WM_PAINT://窗口重绘消息hdc = BeginPaint(hWnd, &ps);MyPaint(hdc);EndPaint(hWnd, &ps);break;case WM_DESTROY://窗口结束消息DeleteDC(mdc);DeleteObject(hbmp);PostQuitMessage(0);break;default://其他消息return DefWindowProc(hWnd, message, wParam, lParam);   }   return 0;}

hdc = GetDC(hWnd);
mdc = CreateCompatibleDC(hdc);
hbmp = (HBITMAP)LoadImage(NULL,"bg.bmp",IMAGE_BITMAP,800,600,LR_LOADFROMFILE); 
SelectObject(mdc,hbmp);

MyPaint(hdc);

MyPaint只有一条语句BitBlt(hdc,0,0,800,600,mdc,0,0,SRCCOPY);//采用BitBlt函数贴图

BitBlt就是我们要讲的一个块传送函数。而后面的一个参数SRCCOPY就类似我么之前讲到的ROP码。就是我么讲的块传送方式。

我们在操作位图时,要创建一个与目标DC兼容的内存DC,这里的CreateCompatibleDC就是用来创建内存DC的。然后所有对这个DC的操作都是对位图的操作,最后我们在用BitBlt函数将整个内存DC的内容按照SRCCOPY的方式去复制到目标DC上面去。位图的整个过程归结起来如下:用LoadImage载入获得位图句柄,接着创建内存DC,将位图选入创建好的内存DC中,对这个DC进行操作。最后再按照特定的块传送方式利用特定的块传送函数传送的目标DC中。

OK,我们看看windows定义的块传送方式,这类似我们的ROP2码。不过名称不一样。都是进行位运算等


单单看块传送方式我们是一点点感觉 都没有,要结合块传送函数来看才好。ROP码要是使用得牛逼,那能做相当牛逼的设计。

块传送函数有PatBlt,BitBlt,MaskBlt,PlgBlt,StretchBlt和TransparentBlt

块传送函数我们主要看BitBlt函数的使用。其他的可以参照MSDN。这里引用浅墨的BitBlt函数以及结合操作码来实现透明效果。由于u盘丢失,本人也是通过这样来避免下次来图书馆还得重新下代码的,所以写博客还能记录以前的代码,不然代码再重新写起来,那真是要命得很。废话少说,代码如下。

#include <windows.h>//全局变量声明HINSTANCE hInst;HBITMAP bg,dra;        //声明两个位图对象,分别存储背景图与前景恐龙图HDC mdc;       //声明一个内存DC"mdc",用来暂存位图//全局函数声明ATOM MyRegisterClass(HINSTANCE hInstance);BOOL InitInstance(HINSTANCE, int);LRESULT CALLBACKWndProc(HWND, UINT, WPARAM, LPARAM);void MyPaint(HDC hdc);////****Winmain函数,程序入口点函数**************************************int APIENTRY WinMain(HINSTANCE hInstance,                     HINSTANCE hPrevInstance,                     LPSTR     lpCmdLine,                     int       nCmdShow){MSG msg;MyRegisterClass(hInstance);if (!InitInstance (hInstance, nCmdShow)) {return FALSE;}//消息循环while (GetMessage(&msg, NULL, 0, 0)) {TranslateMessage(&msg);DispatchMessage(&msg);}return msg.wParam;}//****设计一个窗口类,类似填空题,使用窗口结构体*************************ATOM MyRegisterClass(HINSTANCE hInstance){WNDCLASSEX wcex;wcex.cbSize = sizeof(WNDCLASSEX); wcex.style = CS_HREDRAW | CS_VREDRAW;wcex.lpfnWndProc= (WNDPROC)WndProc;wcex.cbClsExtra = 0;wcex.cbWndExtra = 0;wcex.hInstance = hInstance;wcex.hIcon = NULL;wcex.hCursor = NULL;wcex.hCursor = LoadCursor(NULL, IDC_ARROW);wcex.hbrBackground= (HBRUSH)(COLOR_WINDOW+1);wcex.lpszMenuName= NULL;wcex.lpszClassName= "canvas";wcex.hIconSm = NULL;return RegisterClassEx(&wcex);}//****初始化函数*************************************// 1.建立与窗口DC兼容的内存DC// 2.从文件加载背景图与恐龙图BOOL InitInstance(HINSTANCE hInstance, int nCmdShow){HWND hWnd;HDC hdc;hInst = hInstance;hWnd = CreateWindow("canvas", "绘图窗口" , WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);if (!hWnd){return FALSE;}MoveWindow(hWnd,10,10,600,450,true);ShowWindow(hWnd, nCmdShow);UpdateWindow(hWnd);hdc = GetDC(hWnd);                     //获得窗口DCmdc = CreateCompatibleDC(hdc);           //创建与窗口兼容的内存DC(mdc)bg = (HBITMAP)LoadImage(NULL,"bg.bmp",IMAGE_BITMAP,600,450,LR_LOADFROMFILE); //J加载背景图到bg中dra = (HBITMAP)LoadImage(NULL,"dra.bmp",IMAGE_BITMAP,170,99,LR_LOADFROMFILE); //加载恐龙图到dra中MyPaint(hdc);ReleaseDC(hWnd,hdc);return TRUE;}//****自定义绘图函数*********************************//透明贴图void MyPaint(HDC hdc){SelectObject(mdc,bg);BitBlt(hdc,0,0,600,450,mdc,0,0,SRCCOPY);    //先将背景图贴到显示窗口中SelectObject(mdc,dra);                      //选用恐龙图到"mdc"中BitBlt(hdc,280,320,85,99,mdc,85,0,SRCAND);//进行制作贴图的第一步骤,即将屏蔽图与背景图做"AND"运算,屏蔽图在整张恐龙图中,最左上角起始位置点得坐标为(85,0),BitBlt()函数中最后一个Raster参数值设置为SRCAND。BitBlt(hdc,280,320,85,99,mdc,0,0,SRCPAINT);//进行制作透明贴图的第二步骤,即将前景图与背景图做"OR"运算,前景图在整张恐龙图中,最左上角起始位置的坐标为(0,0),BitBlt()函数最后一个参数值设置为SRCPAINT。}//****消息处理函数**********************************LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam){PAINTSTRUCT ps;HDC hdc;switch (message){case WM_PAINT: //窗口重绘消息hdc = BeginPaint(hWnd, &ps);MyPaint(hdc);EndPaint(hWnd, &ps);break;case WM_DESTROY: //窗口结束消息DeleteDC(mdc);DeleteObject(bg);DeleteObject(dra);PostQuitMessage(0);break;default: //其他消息return DefWindowProc(hWnd, message, wParam, lParam);   }   return 0;}

最后也附上一个利用GDI来实现屏幕截图的代码。网上的,本人也正在研究这个代码并试图改进,使得效果好点,如果有幸做好,代码自然会贴出来与大家分享。^_^

#include <windows.h>void echo(CHAR *str);int CaptureImage(HWND hWnd, CHAR *dirPath, CHAR *filename);int main(){echo("准备截图");CaptureImage(GetDesktopWindow(), "C:\\", "hello"); // 保存为 E:\hello.bmpecho("截图结束");return 0;}/** * 调试输出 */void echo(CHAR *str) {MessageBox(NULL, str, NULL, MB_OK);}/** * GDI 截取指定窗口 *  * 参数 hwnd 要截屏的窗口句柄 * 参数 dirPath 截图存放目录 * 参数 filename 截图名称 */int CaptureImage(HWND hwnd, CHAR *dirPath, CHAR *filename){HDCmdc;HBITMAP hbmp;CHAR FilePath[MAX_PATH];HDC hdcScreen;HDC hdcWindow;HDC hdcMemDC = NULL;HBITMAP hbmScreen = NULL;BITMAP bmpScreen;RECT rcClient;BITMAPFILEHEADER   bmfHeader;    BITMAPINFOHEADER   bi;DWORD dwBmpSize;HANDLE hDIB;CHAR *lpbitmap;HANDLE hFile;DWORD dwSizeofDIB;DWORD dwBytesWritten;hdcScreen = GetDC(NULL); // 全屏幕DChdcWindow = GetDC(hwnd); // 截图目标窗口DC// 创建兼容内存DChdcMemDC = CreateCompatibleDC(hdcWindow); if(!hdcMemDC){echo(TEXT("CreateCompatibleDC has failed"));goto done;}// 获取客户端区域用于计算大小GetClientRect(hwnd, &rcClient);// 设置延展模式SetStretchBltMode(hdcWindow, HALFTONE);// 来源 DC 是整个屏幕而目标 DC 是当前的窗口 (HWND)if(!StretchBlt(hdcWindow, 0,0, rcClient.right, rcClient.bottom, hdcScreen, 0,0,GetSystemMetrics (SM_CXSCREEN),GetSystemMetrics (SM_CYSCREEN),SRCCOPY)){echo(TEXT("StretchBlt has failed"));goto done;}// 通过窗口DC 创建一个兼容位图hbmScreen = CreateCompatibleBitmap(hdcWindow,rcClient.right-rcClient.left,rcClient.bottom-rcClient.top);if(!hbmScreen){echo(TEXT("CreateCompatibleBitmap Failed"));goto done;}// 将位图块传送到我们兼容的内存DC中SelectObject(hdcMemDC,hbmScreen);if(!BitBlt(hdcMemDC,// 目的DC0,0,// 目的DC的 x,y 坐标rcClient.right - rcClient.left, rcClient.bottom - rcClient.top, // 目的 DC 的宽高hdcWindow,  // 来源DC0,0,// 来源DC的 x,y 坐标SRCCOPY))// 粘贴方式{echo(TEXT("BitBlt has failed"));goto done;}// 获取位图信息并存放在 bmpScreen 中GetObject(hbmScreen,sizeof(BITMAP),&bmpScreen);bi.biSize = sizeof(BITMAPINFOHEADER);    bi.biWidth = bmpScreen.bmWidth;    bi.biHeight = bmpScreen.bmHeight;  bi.biPlanes = 1;    bi.biBitCount = 32;    bi.biCompression = BI_RGB;    bi.biSizeImage = 0;  bi.biXPelsPerMeter = 0;    bi.biYPelsPerMeter = 0;    bi.biClrUsed = 0;    bi.biClrImportant = 0;dwBmpSize = ((bmpScreen.bmWidth * bi.biBitCount + 31) / 32) * 4 * bmpScreen.bmHeight;// 在 32-bit Windows 系统上, GlobalAlloc 和 LocalAlloc 是由 HeapAlloc 封装来的// handle 指向进程默认的堆. 所以开销比 HeapAlloc 要大hDIB = GlobalAlloc(GHND,dwBmpSize); lpbitmap = (char *)GlobalLock(hDIB);    // 获取兼容位图的位并且拷贝结果到一个 lpbitmap 中.GetDIBits(hdcWindow,  // 设备环境句柄hbmScreen,  // 位图句柄0,// 指定检索的第一个扫描线(UINT)bmpScreen.bmHeight, // 指定检索的扫描线数lpbitmap,// 指向用来检索位图数据的缓冲区的指针(BITMAPINFO *)&bi, // 该结构体保存位图的数据格式DIB_RGB_COLORS // 颜色表由红、绿、蓝(RGB)三个直接值构成);wsprintf(FilePath, "%s\\%s.bmp", dirPath, filename);// 创建一个文件来保存文件截图hFile = CreateFile(FilePath,GENERIC_WRITE,0,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);// 将 图片头(headers)的大小, 加上位图的大小来获得整个文件的大小dwSizeofDIB = dwBmpSize + sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);// 设置 Offset 偏移至位图的位(bitmap bits)实际开始的地方bmfHeader.bfOffBits = (DWORD)sizeof(BITMAPFILEHEADER) + (DWORD)sizeof(BITMAPINFOHEADER); // 文件大小bmfHeader.bfSize = dwSizeofDIB; // 位图的 bfType 必须是字符串 "BM"bmfHeader.bfType = 0x4D42; //BM   dwBytesWritten = 0;WriteFile(hFile, (LPSTR)&bmfHeader, sizeof(BITMAPFILEHEADER), &dwBytesWritten, NULL);WriteFile(hFile, (LPSTR)&bi, sizeof(BITMAPINFOHEADER), &dwBytesWritten, NULL);WriteFile(hFile, (LPSTR)lpbitmap, dwBmpSize, &dwBytesWritten, NULL);// 解锁堆内存并释放GlobalUnlock(hDIB);    GlobalFree(hDIB);// 关闭文件句柄CloseHandle(hFile);// 清理资源done:DeleteObject(hbmScreen);DeleteObject(hdcMemDC);ReleaseDC(NULL,hdcScreen);ReleaseDC(hwnd,hdcWindow);return 0;}

这个程序创建的时候要用win32控制台工程。不然链接出错,找不到WinMain入口函数。


0 0