DIB(设备无关位图)是存储在磁盘上的位图文件,可以从磁盘读到内存中或从内存保存到磁盘上,它的磁盘文件结构是标准化的,在Linux、Unix及Windows上都可以以同样效果显示。位图是最接近硬件的图像格式,Windows显示的核心是位图,它的SDK API专门提供了一组用于操作DIB文件的函数。但是由于这样或那样的原因,高效合理的使用这些DIB API是需要了解不少历史和使用背景的,在这里我抽茧剥丝介绍和演示DIB的使用,相信对你更好的使用DIB文件有帮助,由于DIB函数比较多,这里分为三部分介绍,首先是DIB的读入、保存和显示。
1.DIB文件的组成
关于DIB文件的组成很多地方有详细描述,这里不再详细赘述。主要分为如下几个部分:
- 文件表头
- 信息表头
- RGB色彩对照表(不一定有)
- 位图像素位
其中
- 文件表头包含了文件类型(BMP)、文件大小及文件中实际像素数据保存的偏移值
- 信息头包含了位图的大小、位面数、位深度、压缩及掩码等信息
- RGB色彩对照表也称调色板,8位以下位图可能包含有调色板信息,16位及以上一般没有调色板
- 位图像素位为实际位图数据保存区
其中,信息表头和RGB调色板合称为位图信息。
2.Windows DIB内存数据结构
我们要把DIB数据读入到内存中,那么就要分配相应的内存,把读入的数据写到对应的内存区中,这里SDK 提供的数据结构是各种结构体,结构体的各个字段对应磁盘文件中各个信息值。我们这里为了逻辑清楚,使用最常使用的DIB结构体。
如图,文件中的数据读到对应的内存结构中完成文件的读入,内存结构中的数据写到对应的文件中完成文件的保存。
其中
- 文件信息头数据读到BITMAPFILEHEADER结构体中
- 位图信息头读到BITMAPINFOHEADER结构体中
- 位图调色板读到RGBQUAD结构体数组中
- 位图数据读到根据位图信息头提供的信息而分配的相应大小的数据区中
把BITMAPINFOHEADER和RGBQUAD[0]作为BITMAPINFO结构体成员,这样一方面是为了和磁盘文件对应,另一方面也是为了访问调色板数据方便。
这里的磁盘文件中各个段在磁盘上保存位置必须是连续的,但是对应的内存中文件信息头、位图信息和位图数据三大块不一定要是连续的。因此,在读入文件中你既可以一次性读入磁盘文件到连续的内存中,也可以分开读入到三个分别连续的内存中,后面会做相关演示。
3.DIB显示
前面讲了读入DIB到对应的内存结构中,现在我们要怎么把对应的内存结构中的像素数据显示出来呢?
如下图,为内存中指定点的像素值是怎么样一步步显示到显示器上指定位置的,这里做了一些简化,没有考虑压缩位图和显示器的显示精度
这个图看起来很复杂,实际上在这里我也没想把它完全讲出来,如果你之前有一定的DIB使用经验,那么这幅图可以帮你更好的理解整个显示过程,如果没有DIB使用经验,那么不用仔细看这幅位图。从这幅图,我们知道显示的时候需要的数据是:内存中位图数据、内存中位图信息、内存中位图指定要显示区域、显示器上用于显示的区域。
提供了需要的信息后,Windows提供了SetDIBitsToDevice和StretchDIBits函数帮我们完成这一显示过程。
两个函数的原型分别如下
int SetDIBitsToDevice( HDC hdc, // handle to DC int XDest, // x-coord of destination upper-left corner int YDest, // y-coord of destination upper-left corner DWORD dwWidth, // source rectangle width DWORD dwHeight, // source rectangle height int XSrc, // x-coord of source lower-left corner int YSrc, // y-coord of source lower-left corner UINT uStartScan, // first scan line in array UINT cScanLines, // number of scan lines CONST VOID *lpvBits, // array of DIB bits CONST BITMAPINFO *lpbmi, // bitmap information UINT fuColorUse // RGB or palette indexes );
int StretchDIBits( HDC hdc, // handle to DC int XDest, // x-coord of destination upper-left corner int YDest, // y-coord of destination upper-left corner int nDestWidth, // width of destination rectangle int nDestHeight, // height of destination rectangle int XSrc, // x-coord of source upper-left corner int YSrc, // y-coord of source upper-left corner int nSrcWidth, // width of source rectangle int nSrcHeight, // height of source rectangle CONST VOID *lpBits, // bitmap bits CONST BITMAPINFO *lpBitsInfo, // bitmap data UINT iUsage, // usage options DWORD dwRop // raster operation code );
4.代码演示
这里的演示程序界面如下
在读入中可以选择一次性读入还是分片段读入,两种方式的区别在于读入以后在内存中位图各个段是否连续。保存也对应读入的两种方式。显示可以选择正常居中显示还是拉伸到整个窗口屏幕显示,前者对应SetDIBitsToDevice函数,后者对应StretchDIBits函数。这里只提供一次性读入、保存和显示代码,完整代码参见附件。
BOOL DibTotalLoad(PTSTR szBmpFile, PBITMAPFILEHEADER *ppbmfh, PBITMAPINFO *ppbmi, PBYTE *ppBits, PLONG pBmpWidth, PLONG pBmpHeight) { HANDLE hFile; DWORD dwFileSize, dwBytesRead; BOOL bSuccess; //打开文件 hFile = CreateFile( szBmpFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL); if (INVALID_HANDLE_VALUE == hFile) { return FALSE; } //判断BMP文件大小(所有部分) dwFileSize = GetFileSize(hFile, NULL); //分配对应大小内存用于保存磁盘BMP文件内容 *ppbmfh = malloc(dwFileSize); if (!(*ppbmfh)) { CloseHandle(hFile); return FALSE; } //读入文件内容 bSuccess = ReadFile(hFile, *ppbmfh, dwFileSize, &dwBytesRead, NULL); CloseHandle(hFile); //校验读入是否正确和文件是否为BMP文件 if (!bSuccess || (dwBytesRead != dwFileSize) || (*ppbmfh)->bfType != *(WORD *)"BM") { free(*ppbmfh); return FALSE; } //计算剩余的返回参数 *ppbmi = (PBITMAPINFO)(*ppbmfh+1); *ppBits = (PBYTE)(*ppbmfh) + (*ppbmfh)->bfOffBits; *pBmpWidth = (*ppbmi)->bmiHeader.biWidth; *pBmpHeight = (*ppbmi)->bmiHeader.biHeight; return TRUE; } BOOL DibTotalSave(PTSTR szBmpFile, PBITMAPFILEHEADER pbmfh) { BOOL bSuccess; DWORD dwBytesWrite; HANDLE hFile; //打开要写入的文件 hFile = CreateFile(szBmpFile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (INVALID_HANDLE_VALUE == hFile) { return FALSE; } //写入文件 bSuccess = WriteFile(hFile, pbmfh, pbmfh->bfSize, &dwBytesWrite, NULL); CloseHandle(hFile); if (!bSuccess || dwBytesWrite != pbmfh->bfSize) { DeleteFile(hFile); return FALSE; } return TRUE; } void ShowDib(HDC hdc, PBITMAPINFO pbmi, PBYTE pBits, long nBmpWidth, long nBmpHeight, long cxClient, long cyClient, BOOL bFull) { if (FALSE == bFull)//居中显示 { SetDIBitsToDevice( hdc, (cxClient-nBmpWidth)/2, (cyClient-nBmpHeight)/2, nBmpWidth, nBmpHeight, 0, 0, 0, nBmpHeight, pBits, pbmi, DIB_RGB_COLORS); } else//拉伸显示 { SetStretchBltMode(hdc, COLORONCOLOR); StretchDIBits(hdc, 0, 0, cxClient, cyClient, 0, 0, nBmpWidth, nBmpHeight, pBits, pbmi, DIB_RGB_COLORS, SRCCOPY); } }
这里需要补充说明的是,为了保证逻辑清晰,本文没有考虑位图的OS/2兼容格式和自顶向下位图等特殊情况,错误处理可能也不是特别完善(没有考虑超大位图的情况),演示的代码在大部分情况下是适用的,只是为了起抛砖引玉的作用。关于DIB的详细描述当推Petzold的《Windows 程序设计》的“与设备无关的位图”一章,这一章描述非常详细,但是个别地方有些晦涩,结合本文来看可以加深理解。
完整源代码下载链接
原创,转载请注明来自http://blog.csdn.net/wenzhou1219