位图,有时候也被称之为栅栏图、栅格图,是一种保存着图像每一位像素的格式。姑且这么说吧。以此相对的有矢量图,矢量图是用数学公式保存着每一点、每一条线条的方式来存储图像的。
在windows操作系统中,我们都可以使用微软定义的bmp位图(Bitmap)文件。位图有压缩的,也有没压缩的。我们这里主要看没压缩的。
Bitmap位图文件的结构主要由三部分组成。首先在文件的头部是BITMAPFILEHEADER,接着的是BITMAPINFOHEADER,下面是相关结构体的声明。
typedef struct tagBITMAPFILEHEADER {
WORD bfType; //指定文件类型,对于位图文件,这里必须为BM即 0x4D42
DWORD bfSize; // BITMAPFILEHEADER结构体的字节大小
WORD bfReserved1; //必须为0
WORD bfReserved2; //必须为0
DWORD bfOffBits; //像素的偏移地址
} BITMAPFILEHEADER, *PBITMAPFILEHEADER;
typedef struct tagBITMAPINFOHEADER {
DWORD biSize; // BITMAPINFOHEADER结构体的字节大小
LONG biWidth; //图像的宽度
LONG biHeight; //图像的高度
WORD biPlanes; //目标设备平面数目,必须为1
WORD biBitCount; //每个像素的颜色位数,可取1,4,8,16,24,32
DWORD biCompression; //压缩类型,我们主要是关注没有压缩的,即BI_RGB
DWORD biSizeImage; //图像数据的字节大小(不包括两个头部)
LONG biXPelsPerMeter;
LONG biYPelsPerMeter;
DWORD biClrUsed;
DWORD biClrImportant;
} BITMAPINFOHEADER;
在上面的两个结构体重,我们知道了怎么辨别一个位图文件,也知道了像素的开始偏移,图像的宽度、高度,图像数据的字节大小。如此,就好办了。
有一个需要注意下一,位图文件保存的颜色数据是BGR的顺序,而我们在内存中通常使用的是RGB的顺序,所以必要时,我们需要手动转换下顺序。
下面是加载位图文件的图像像素数据的示例函数
// LoadBitmapFile
//加载filename指定的位图文件于内存中,并通过参数bitmapInfoHeader返回BITMAPINFOHEADER头部
//返回值:图像数据,使用完之后需自行释放内存
//不支持8bits的位图文件
unsigned char *LoadBitmapFile(char *filename, BITMAPINFOHEADER *bitmapInfoHeader)
{
FILE *filePtr; // the file pointer
BITMAPFILEHEADER bitmapFileHeader; // bitmap file header
unsigned char *bitmapImage; // bitmap image data
int imageIdx = 0; // image index counter
unsigned char tempRGB; // swap variable
// open filename in "read binary" mode
filePtr = fopen(filename, "rb");
if (filePtr == NULL)
return NULL;
// read the bitmap file header
fread(&bitmapFileHeader, sizeof(BITMAPFILEHEADER), 1, filePtr);
// verify that this is a bitmap by checking for the universal bitmap id
if (bitmapFileHeader.bfType != BITMAP_ID)
{
fclose(filePtr);
return NULL;
}
// read the bitmap information header
fread(bitmapInfoHeader, sizeof(BITMAPINFOHEADER), 1, filePtr);
// move file pointer to beginning of bitmap data
fseek(filePtr, bitmapFileHeader.bfOffBits, SEEK_SET);
// allocate enough memory for the bitmap image data
bitmapImage = (unsigned char*)malloc(bitmapInfoHeader->biSizeImage);
// verify memory allocation
if (!bitmapImage)
{
free(bitmapImage);
fclose(filePtr);
return NULL;
}
// read in the bitmap image data
fread(bitmapImage, 1, bitmapInfoHeader->biSizeImage, filePtr);
// make sure bitmap image data was read
if (bitmapImage == NULL)
{
fclose(filePtr);
return NULL;
}
// swap the R and B values to get RGB since the bitmap color format is in BGR
for (imageIdx = 0; imageIdx < bitmapInfoHeader->biSizeImage; imageIdx+=3)
{
tempRGB = bitmapImage[imageIdx];
bitmapImage[imageIdx] = bitmapImage[imageIdx + 2];
bitmapImage[imageIdx + 2] = tempRGB;
}
// close the file and return the bitmap image data
fclose(filePtr);
return bitmapImage;
}
加载完图像数据之后,就可以显示出来了。
显示图像,在opengl中我们有glDrawPixels,可以将图像像素绘制出来。
void glDrawPixels(
GLsizei width,
GLsizei height,
GLenum format,
GLenum type,
const GLvoid *pixels //图像数据
);
看到这里,我们发现,只有指定宽度跟高度。起点位置似乎没有办法在这个函数里头指定。
类似于glTranslate函数,我们可以使用glRasterPos来指定图像的起始位置(左下角)。需要注意一下,opengl是以左下角为原点,以右、上分别为x、y轴的正方向。以屏幕正对向外(也即向着用户)的方向为z轴的正方向。
void glRasterPos2i(
GLint x,
GLint y
);
至此,还有个问题。有时候当我们将一个程序从台式电脑移到笔记本电脑上,执行速度可能会不一样。排除硬件速度的不一样外,可能也跟一个内存对齐问题有关。所以这里还需要使用glPixelStorei指定下内存对齐大小。默认是4KB。
void glPixelStorei(
GLenum pname,
GLint param
);
pname取GL_UNPACK_ALIGNMENT时,代表读取内存时的对齐大小。一直相对的GL_PACK_ALIGNMENT,代表加载进内存时的对齐大小。
当pname取以上两个值之一时,param可取1,2,4,8,分别代表对齐大小为1,2,4,8字节。
如此,下面看下绘图的函数。
// DrawBitmap
// 在(0,0)位置绘制width长,height宽的图像
//bitmapImage是图像的数据
void DrawBitmap(long width, long height, unsigned char* bitmapImage)
{
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
//于原点绘图
glRasterPos2i(0,0);
glDrawPixels(width, height, GL_RGB, GL_UNSIGNED_BYTE, bitmapImage);
}
有此两个函数,程序已能从文件里头加载位图文件,并显示出来了。
在上面,我们知道了位图的文件结构。那么,我们想将一段图像数据保存为位图文件,也已经没了什么难度了。下面看下代码。
//WriteBitmapFile
//根据bitmapData的(RGB)数据,保存bitmap
//filename是要保存到物理硬盘的文件名(包括路径)
BOOL WriteBitmapFile(char * filename,int width,int height,unsigned char * bitmapData)
{
//填充BITMAPFILEHEADER
BITMAPFILEHEADER bitmapFileHeader;
memset(&bitmapFileHeader,0,sizeof(BITMAPFILEHEADER));
bitmapFileHeader.bfSize = sizeof(BITMAPFILEHEADER);
bitmapFileHeader.bfType = 0x4d42; //BM
bitmapFileHeader.bfOffBits =sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
//填充BITMAPINFOHEADER
BITMAPINFOHEADER bitmapInfoHeader;
memset(&bitmapInfoHeader,0,sizeof(BITMAPINFOHEADER));
bitmapInfoHeader.biSize = sizeof(BITMAPINFOHEADER);
bitmapInfoHeader.biWidth = width;
bitmapInfoHeader.biHeight = height;
bitmapInfoHeader.biPlanes = 1;
bitmapInfoHeader.biBitCount = 24;
bitmapInfoHeader.biCompression = BI_RGB;
bitmapInfoHeader.biSizeImage = width * abs(height) * 3;
//
FILE * filePtr; //连接要保存的bitmap文件用
unsigned char tempRGB; //临时色素
int imageIdx;
//交换R、B的像素位置,bitmap的文件放置的是BGR,内存的是RGB
for (imageIdx = 0;imageIdx < bitmapInfoHeader.biSizeImage;imageIdx +=3)
{
tempRGB = bitmapData[imageIdx];
bitmapData[imageIdx] = bitmapData[imageIdx + 2];
bitmapData[imageIdx + 2] = tempRGB;
}
filePtr = fopen(filename,"wb");
if (NULL == filePtr)
{
return FALSE;
}
fwrite(&bitmapFileHeader,sizeof(BITMAPFILEHEADER),1,filePtr);
fwrite(&bitmapInfoHeader,sizeof(BITMAPINFOHEADER),1,filePtr);
fwrite(bitmapData,bitmapInfoHeader.biSizeImage,1,filePtr);
fclose(filePtr);
return TRUE;
}
有了上面的基础,这里也不需要多说什么了。
接下来,就是读取窗口中的图像数据了。在opengl中,有个glReadPixels可以帮到我们。glReadPixels能够读取窗口块的图像数据
void glReadPixels(
GLint x, //
GLint y, //起始位置
GLsizei width, //宽度
GLsizei height, //高度
GLenum format,
GLenum type,
GLvoid *pixels //保存的内存地址
);
下面是读取窗口图像数据的函数。
//SaveScreenShot
//保存窗口客户端的截图
//窗口大小* 600
void SaveScreenShot()
{
int clnWidth,clnHeight; //client width and height
static void * screenData;
RECT rc;
int len = 800 * 600 * 3;
screenData = malloc(len);
memset(screenData,0,len);
glReadPixels(0, 0, 800, 600, GL_RGB, GL_UNSIGNED_BYTE, screenData);
//生成文件名字符串,以时间命名
time_t tm = 0;
tm = time(NULL);
char lpstrFilename[256] = {0};
sprintf_s(lpstrFilename,sizeof(lpstrFilename),"%d.bmp",tm);
WriteBitmapFile(lpstrFilename,800,600,(unsigned char*)screenData);
//释放内存
free(screenData);
}
至此,已经可以读取、显示、截图、保存位图文件了。下面看一下完整的代码。
#include "stdafx.h"
#define WIN32_LEAN_AND_MEAN // trim the excess fat from Windows
// Defines
#define BITMAP_ID 0x4D42 // the universal bitmap ID
// Includes
#include <windows.h> // standard Windows app include
#include <stdio.h>
#include <gl/gl.h> // standard OpenGL include
#include <gl/glu.h> // OpenGL utilties
#include <time.h>
#include <windef.h>
#include <string.h>
// Global Variables
HDC g_HDC; // global device context
bool fullScreen = false; // true = fullscreen; false = windowed
bool keyPressed[256]; // holds true for keys that are pressed
// Bitmap Information
BITMAPINFOHEADER bitmapInfoHeader; // bitmap info header
unsigned char* bitmapData; // the bitmap data
//
//全局函数声明
//WriteBitmapFile
//根据bitmapData的(RGB)数据,保存bitmap
//filename是要保存到物理硬盘的文件名(包括路径)
BOOL WriteBitmapFile(char * filename,int width,int height,unsigned char * bitmapData);
//SaveScreenShot
//保存窗口客户端的截图
//窗口大小* 600
void SaveScreenShot();
//
// DrawBitmap
// desc: draws the bitmap image data in bitmapImage at the location
// (350,300) in the window. (350,300) is the lower-left corner
// of the bitmap.
void DrawBitmap(long width, long height, unsigned char* bitmapImage)
{
// glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
// glRasterPos2i(200,200);
// glDrawPixels(width, height, GL_RGB, GL_UNSIGNED_BYTE, bitmapImage);
glPixelStorei(GL_UNPACK_ALIGNMENT,4);
glRasterPos2i(0,0);
glDrawPixels(width,height,GL_RGB,GL_UNSIGNED_BYTE,bitmapImage);
}
// LoadBitmapFile
// desc: Returns a pointer to the bitmap image of the bitmap specified
// by filename. Also returns the bitmap header information.
// No support for 8-bit bitmaps.
unsigned char *LoadBitmapFile(char *filename, BITMAPINFOHEADER *bitmapInfoHeader)
{
FILE *filePtr; // the file pointer
BITMAPFILEHEADER bitmapFileHeader; // bitmap file header
unsigned char *bitmapImage; // bitmap image data
int imageIdx = 0; // image index counter
unsigned char tempRGB; // swap variable
// open filename in "read binary" mode
filePtr = fopen(filename, "rb");
if (filePtr == NULL)
return NULL;
// read the bitmap file header
fread(&bitmapFileHeader, sizeof(BITMAPFILEHEADER), 1, filePtr);
// verify that this is a bitmap by checking for the universal bitmap id
if (bitmapFileHeader.bfType != BITMAP_ID)
{
fclose(filePtr);
return NULL;
}
// read the bitmap information header
fread(bitmapInfoHeader, sizeof(BITMAPINFOHEADER), 1, filePtr);
// move file pointer to beginning of bitmap data
fseek(filePtr, bitmapFileHeader.bfOffBits, SEEK_SET);
// allocate enough memory for the bitmap image data
bitmapImage = (unsigned char*)malloc(bitmapInfoHeader->biSizeImage);
// verify memory allocation
if (!bitmapImage)
{
free(bitmapImage);
fclose(filePtr);
return NULL;
}
// read in the bitmap image data
fread(bitmapImage, 1, bitmapInfoHeader->biSizeImage, filePtr);
// make sure bitmap image data was read
if (bitmapImage == NULL)
{
fclose(filePtr);
return NULL;
}
// swap the R and B values to get RGB since the bitmap color format is in BGR
for (imageIdx = 0; imageIdx < bitmapInfoHeader->biSizeImage; imageIdx+=3)
{
tempRGB = bitmapImage[imageIdx];
bitmapImage[imageIdx] = bitmapImage[imageIdx + 2];
bitmapImage[imageIdx + 2] = tempRGB;
}
// close the file and return the bitmap image data
fclose(filePtr);
return bitmapImage;
}
// Initialize
// desc: initializes OpenGL
void Initialize()
{
// enable depth buffer, and backface culling
glEnable(GL_DEPTH_TEST);
glEnable(GL_CULL_FACE);
// Clear background to black
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
// load our bitmap file
bitmapData = LoadBitmapFile("test.bmp", &bitmapInfoHeader);
}
// Render
// desc: handles drawing of scene
void Render()
{
// clear screen and depth buffer
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// draw the bitmap image
DrawBitmap(bitmapInfoHeader.biWidth, bitmapInfoHeader.biHeight, bitmapData);
glFlush();
SwapBuffers(g_HDC); // bring backbuffer to foreground
}
// function to set the pixel format for the device context
void SetupPixelFormat(HDC hDC)
{
int nPixelFormat; // our pixel format index
static PIXELFORMATDESCRIPTOR pfd = {
sizeof(PIXELFORMATDESCRIPTOR), // size of structure
1, // default version
PFD_DRAW_TO_WINDOW | // window drawing support
PFD_SUPPORT_OPENGL | // OpenGL support
PFD_DOUBLEBUFFER, // double buffering support
PFD_TYPE_RGBA, // RGBA color mode
32, // 32 bit color mode
0, 0, 0, 0, 0, 0, // ignore color bits, non-palettized mode
0, // no alpha buffer
0, // ignore shift bit
0, // no accumulation buffer
0, 0, 0, 0, // ignore accumulation bits
16, // 16 bit z-buffer size
0, // no stencil buffer
0, // no auxiliary buffer
PFD_MAIN_PLANE, // main drawing plane
0, // reserved
0, 0, 0 }; // layer masks ignored
nPixelFormat = ChoosePixelFormat(hDC, &pfd); // choose best matching pixel format
SetPixelFormat(hDC, nPixelFormat, &pfd); // set pixel format to device context
}
// the Windows Procedure event handler
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static HGLRC hRC; // rendering context
static HDC hDC; // device context
int width, height; // window width and height
switch(message)
{
case WM_CREATE: // window is being created
// hDC = GetDC(hwnd); // get current window's device context
// g_HDC = hDC;
// SetupPixelFormat(hDC); // call our pixel format setup function
//
// // create rendering context and make it current
// hRC = wglCreateContext(hDC);
// wglMakeCurrent(hDC, hRC);
//
// return 0;
g_HDC = GetDC(hwnd);
SetupPixelFormat(g_HDC);
hRC = wglCreateContext(g_HDC);
wglMakeCurrent(g_HDC,hRC);
break;
case WM_DESTROY:
case WM_CLOSE: // windows is closing
// deselect rendering context and delete it
wglMakeCurrent(hDC, NULL);
wglDeleteContext(hRC);
ExitProcess(NULL);
// send WM_QUIT to message queue
PostQuitMessage(0);
return 0;
break;
case WM_SIZE:
width = LOWORD(lParam);
height = HIWORD(lParam);
glViewport(0,0,width,height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
//2D otho projection
glOrtho(0.0f, width - 1.0, 0.0, height - 1.0, -1.0, 1.0);
// glOrtho(0.0f,width,0.0f,height,-1.0f,1.0f);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
return 0;
break;
case WM_KEYDOWN: // is a key pressed?
keyPressed[wParam] = true;
return 0;
break;
case WM_KEYUP:
keyPressed[wParam] = false;
return 0;
break;
default:
break;
}
return (DefWindowProc(hwnd, message, wParam, lParam));
}
// the main windows entry point
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
WNDCLASSEX windowClass; // window class
HWND hwnd; // window handle
MSG msg; // message
bool done; // flag saying when our app is complete
DWORD dwExStyle; // Window Extended Style
DWORD dwStyle; // Window Style
RECT windowRect;
// temp var's
int width = 800;
int height = 600;
int bits = 32;
//fullScreen = TRUE;
windowRect.left=(long)0; // Set Left Value To 0
windowRect.right=(long)width; // Set Right Value To Requested Width
windowRect.top=(long)0; // Set Top Value To 0
windowRect.bottom=(long)height; // Set Bottom Value To Requested Height
// fill out the window class structure
windowClass.cbSize = sizeof(WNDCLASSEX);
windowClass.style = CS_HREDRAW | CS_VREDRAW;
windowClass.lpfnWndProc = WndProc;
windowClass.cbClsExtra = 0;
windowClass.cbWndExtra = 0;
windowClass.hInstance = hInstance;
windowClass.hIcon = LoadIcon(NULL, IDI_APPLICATION); // default icon
windowClass.hCursor = LoadCursor(NULL, IDC_ARROW); // default arrow
windowClass.hbrBackground = NULL; // don't need background
windowClass.lpszMenuName = NULL; // no menu
windowClass.lpszClassName = "MyClass";
windowClass.hIconSm = LoadIcon(NULL, IDI_WINLOGO); // windows logo small icon
// register the windows class
if (!RegisterClassEx(&windowClass))
return 0;
if (fullScreen) // fullscreen?
{
DEVMODE dmScreenSettings; // device mode
memset(&dmScreenSettings,0,sizeof(dmScreenSettings));
dmScreenSettings.dmSize = sizeof(dmScreenSettings);
dmScreenSettings.dmPelsWidth = width; // screen width
dmScreenSettings.dmPelsHeight = height; // screen height
dmScreenSettings.dmBitsPerPel = bits; // bits per pixel
dmScreenSettings.dmFields=DM_BITSPERPEL|DM_PELSWIDTH|DM_PELSHEIGHT;
//
if (ChangeDisplaySettings(&dmScreenSettings, CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL)
{
// setting display mode failed, switch to windowed
MessageBox(NULL, "Display mode failed", NULL, MB_OK);
fullScreen=FALSE;
}
}
if (fullScreen) // Are We Still In Fullscreen Mode?
{
dwExStyle=WS_EX_APPWINDOW; // Window Extended Style
dwStyle=WS_POPUP; // Windows Style
ShowCursor(FALSE); // Hide Mouse Pointer
}
else
{
dwExStyle=WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; // Window Extended Style
dwStyle=WS_OVERLAPPEDWINDOW; // Windows Style
}
AdjustWindowRectEx(&windowRect, dwStyle, FALSE, dwExStyle); // Adjust Window To True Requested Size
// class registered, so now create our window
hwnd = CreateWindowEx(NULL, // extended style
"MyClass", // class name
"Load and Save Bitmap", // app name
dwStyle | WS_CLIPCHILDREN |
WS_CLIPSIBLINGS,
0, 0, // x,y coordinate
windowRect.right - windowRect.left,
windowRect.bottom - windowRect.top, // width, height
NULL, // handle to parent
NULL, // handle to menu
hInstance, // application instance
NULL); // no extra params
// check if window creation failed (hwnd would equal NULL)
if (!hwnd)
return 0;
ShowWindow(hwnd, SW_SHOW); // display the window
UpdateWindow(hwnd); // update the window
done = false; // intialize the loop condition variable
Initialize(); // initialize OpenGL
// main message loop
while (!done)
{
PeekMessage(&msg, hwnd, NULL, NULL, PM_REMOVE);
if (msg.message == WM_QUIT) // do we receive a WM_QUIT message?
{
done = true; // if so, time to quit the application
break;
}
else
{
if (keyPressed['s'] || keyPressed['S'])
{
SaveScreenShot();
keyPressed['s'] = false;
keyPressed['S'] = false;
}
if (keyPressed[VK_ESCAPE])
{
done = true;
break;
}
else
{
Render();
TranslateMessage(&msg); // translate and dispatch to event queue
DispatchMessage(&msg);
}
}
}
free(bitmapData);
if (fullScreen)
{
ChangeDisplaySettings(NULL,0); // If So Switch Back To The Desktop
ShowCursor(TRUE); // Show Mouse Pointer
}
return msg.wParam;
}
//WriteBitmapFile
//根据bitmapData的(RGB)数据,保存bitmap
//filename是要保存到物理硬盘的文件名(包括路径)
BOOL WriteBitmapFile(char * filename,int width,int height,unsigned char * bitmapData)
{
//填充BITMAPFILEHEADER
BITMAPFILEHEADER bitmapFileHeader;
memset(&bitmapFileHeader,0,sizeof(BITMAPFILEHEADER));
bitmapFileHeader.bfSize = sizeof(BITMAPFILEHEADER);
bitmapFileHeader.bfType = 0x4d42; //BM
bitmapFileHeader.bfOffBits =sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
//填充BITMAPINFOHEADER
BITMAPINFOHEADER bitmapInfoHeader;
memset(&bitmapInfoHeader,0,sizeof(BITMAPINFOHEADER));
bitmapInfoHeader.biSize = sizeof(BITMAPINFOHEADER);
bitmapInfoHeader.biWidth = width;
bitmapInfoHeader.biHeight = height;
bitmapInfoHeader.biPlanes = 1;
bitmapInfoHeader.biBitCount = 24;
bitmapInfoHeader.biCompression = BI_RGB;
bitmapInfoHeader.biSizeImage = width * abs(height) * 3;
//
FILE * filePtr; //连接要保存的bitmap文件用
unsigned char tempRGB; //临时色素
int imageIdx;
//交换R、B的像素位置,bitmap的文件放置的是BGR,内存的是RGB
for (imageIdx = 0;imageIdx < bitmapInfoHeader.biSizeImage;imageIdx +=3)
{
tempRGB = bitmapData[imageIdx];
bitmapData[imageIdx] = bitmapData[imageIdx + 2];
bitmapData[imageIdx + 2] = tempRGB;
}
filePtr = fopen(filename,"wb");
if (NULL == filePtr)
{
return FALSE;
}
fwrite(&bitmapFileHeader,sizeof(BITMAPFILEHEADER),1,filePtr);
fwrite(&bitmapInfoHeader,sizeof(BITMAPINFOHEADER),1,filePtr);
fwrite(bitmapData,bitmapInfoHeader.biSizeImage,1,filePtr);
fclose(filePtr);
return TRUE;
}
//SaveScreenShot
//保存窗口客户端的截图
//窗口大小* 600
void SaveScreenShot()
{
int clnWidth,clnHeight; //client width and height
static void * screenData;
RECT rc;
int len = 800 * 600 * 3;
screenData = malloc(len);
memset(screenData,0,len);
glReadPixels(0, 0, 800, 600, GL_RGB, GL_UNSIGNED_BYTE, screenData);
//生成文件名字符串,以时间命名
time_t tm = 0;
tm = time(NULL);
char lpstrFilename[256] = {0};
sprintf_s(lpstrFilename,sizeof(lpstrFilename),"%d.bmp",tm);
WriteBitmapFile(lpstrFilename,800,600,(unsigned char*)screenData);
free(screenData);
}
因篇幅问题不能全部显示,请点此查看更多更全内容