什么是BMP,为什么我们会经常使用这种图片格式?
微软给我们专门提供了一个加载位图的LoadBitmap函数,这说明bmp格式图片在Windows中使用的比较多。
我们知道没一种文件都有它自己的文件结构,那么bmp就是一种位图形式的文件格式。既然bmp也是一种文件,那么我们也可以使用CreateFile函数进行打开。
先看下面一个简单的例子:
void CFileMapBMPDlg::OnBnClickedButtonLoadBmp()
{
Clean();
CFileDialog file_dialog(TRUE);
if (file_dialog.DoModal() == IDOK)
{
m_hFile = CreateFile(file_dialog.GetPathName(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
if (m_hFile != INVALID_HANDLE_VALUE)
{
m_hMap = CreateFileMapping(m_hFile, nullptr, PAGE_READONLY, 0, 0, nullptr);
if (m_hMap != nullptr)
{
m_pBaseAddr = MapViewOfFile(m_hMap, FILE_MAP_READ, 0, 0, 0);
Display();
m_bLoad = TRUE;
}
}
}
}
void CFileMapBMPDlg::Display()
{
BITMAPFILEHEADER *pBitMapHead = (BITMAPFILEHEADER *)m_pBaseAddr;
if (pBitMapHead->bfType == MAKEWORD('B', 'M'))
{
BYTE *pBitMapData = (BYTE *)m_pBaseAddr + pBitMapHead->bfOffBits;
BITMAPINFO* pBitMapInfoHead = (BITMAPINFO*)(((BYTE *)m_pBaseAddr) + sizeof(BITMAPFILEHEADER));
int nWidth = pBitMapInfoHead->bmiHeader.biWidth;
int nHeight = pBitMapInfoHead->bmiHeader.biHeight;
CClientDC dc(this);
HDC hMemDC = CreateCompatibleDC(dc);
HBITMAP hBitmap = CreateCompatibleBitmap(dc, nWidth, nHeight);
SelectObject(hMemDC, hBitmap);
SetDIBitsToDevice(hMemDC, 0, 0, nWidth, nHeight, 0, 0, 0, nHeight, pBitMapData, pBitMapInfoHead, DIB_RGB_COLORS);
BitBlt(dc, 0, 0, nWidth, nHeight, hMemDC, 0, 0, SRCCOPY);
DeleteObject(hBitmap);
DeleteObject(hMemDC);
}
}
void CFileMapBMPDlg::Clean() const
{
if (m_hFile != INVALID_HANDLE_VALUE)
{
CloseHandle(m_hFile);
}
if (m_hMap != nullptr)
{
CloseHandle(m_hMap);
}
if (m_pBaseAddr != nullptr)
{
UnmapViewOfFile(m_pBaseAddr);
}
}
bmp图片格式目前使用的比较广泛,比如大漠插件的图像识别功能就是使用的bmp格式图片。
位图使用的一个例子:如果加载位图的时候,只想加载我们自己位图图片,那么可以在位图的某些特定位置加上特定的像素值,在加载的时候按断这些位置的像素值是否是预设定的值,如果是就可以加载,如果不是就不需要加载了。
我们在操作bmp文件的时候,有三个结构体很关键,BITMAPFILEHEADER、BITMAPINFO和BITMAPINFOHEADER, 正是由于这三个结构体的巧妙设置,才使得经历了这么多年的bmp文件在升级的时候成本降到最小。
typedef struct tagBITMAPFILEHEADER {
WORD bfType;
DWORD bfSize;
WORD bfReserved1;
WORD bfReserved2;
DWORD bfOffBits;
} BITMAPFILEHEADER, *PBITMAPFILEHEADER;
typedef struct tagBITMAPINFO {
BITMAPINFOHEADER bmiHeader;
RGBQUAD bmiColors[1];
} BITMAPINFO, *PBITMAPINFO;
typedef struct tagBITMAPINFOHEADER {
DWORD biSize;
LONG biWidth;
LONG biHeight;
WORD biPlanes;
WORD biBitCount;
DWORD biCompression;
DWORD biSizeImage;
LONG biXPelsPerMeter;
LONG biYPelsPerMeter;
DWORD biClrUsed;
DWORD biClrImportant;
} BITMAPINFOHEADER, *PBITMAPINFOHEADER;
我们发现,BITMAPFILEHEADER结构体做的事情很少,所以它是不需要升级的;但是它可以通过bfOffBits成员找到BITMAPINFO结构体,然后再通过bmiHeader结构体成员的
这是因为BITMAPINFOHEADER结构体中的biSize成员biSize进行结构体取值,如此一来,整个bmp文件的升级成本就会降到最低。通过结构体中的一个size成员变量,就能很好的将软件的升级成本降到最低,这也说明了为什么微软很喜欢在结构体中使用一个标志结构体大小的size变量了。这也能够给我们在开发自己的程序的时候,如何将软件升级的成本将为最低。这个思想值得借鉴!!!
得到位图信息后,我们该如何将这个信息显示到界面上呢?需要使用SetDIBitsToDevice函数进行显示,SetDIBitsToDevice函数在设备上指定的矩形中设置像素,与目标设备上下文关联,使用DIB、JPEG或PNG图像的颜色数据。它的函数原型如下:
int SetDIBitsToDevice(
_In_ HDC hdc,
_In_ int XDest,
_In_ int YDest,
_In_ DWORD dwWidth,
_In_ DWORD dwHeight,
_In_ int XSrc,
_In_ int YSrc,
_In_ UINT uStartScan,
_In_ UINT cScanLines,
_In_ const VOID *lpvBits,
_In_ const BITMAPINFO *lpbmi,
_In_ UINT fuColorUse
);
总结
使用内存映射加载BMP位图比从文件加载(也就是直接使用LoadBitmap函数)加载位图的速度要快很多。这种方式在游戏中用的很广泛。
这种方式必须一次全部加载到内存中,否则就会映射失败。
内存映射的另一个应用-共享空间
它可以共享数据,
我们知道每个进程与进程进制在逻辑上是分离的,但是在表现形式上,这两者又是相关的。所以在两个进程中数据共享是比较麻烦的。我们可以使用文件来共享数据,但是毫无疑问,通过文件来交换数据的速度是非常慢的。所以通过内存映射来共享数据的速度就变得非常的快了。
内存映射是通过内核对象来管理的。使用CreateFile创建内存映射的时候,给最后一个参数指定一个名字,就可以使用这个名字用OpenProcess函数来进行数据共享,但是这样需要注意两点:1.原来的内存映射的HANDLE不能关闭;2.访问冲突的问题,所以需要做同步,使用event就可以了。
我们如果要做进程间通信应该使用管道或者socket。