位图,有时候也被称之为栅栏图、栅格图,是一种保存着图像每一位像素的格式。姑且这么说吧。以此相对的有矢量图,矢量图是用数学公式保存着每一点、每一条线条的方式来存储图像的。
在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); }
原文:http://blog.csdn.net/epluguo/article/details/19014325