[转]CreateDIBitmap与CreateDIBSection

首先明确最主要区别CreateDIBitmap创建的是设备相关位图句柄 - HBITMAP.
                              CreateDIBSection创建的是设备无关位图句柄 - HBITMAP.

DIBDDB之间的相互转换比较慢(关于DIB与DDB区别详见《设备相关(DDB)与设备无关(DIB)》),所以我们使用CreateDIBSection()来创建一个DIB区块。这样作图速度快。 
CreateDIBSection()返回的是一个HBITMAP,CreateDIBitmap()返回的也是HBIT MAP。 
两者的区别在于:CreateDIBSection创建的是一个DIBSECTION结构,
                            CreateDIBitmap创建的是BITMAP结构。 
---------------------------------------------------- 
关于DIB区块(DIBSECTION)详见:http://technet.microsoft.com/zh-cn/library/aa930771
可以看到,它包含了一个 位图结构BITMAP,一个DIB信息头BITMAPINFOHEADER,一个掩码表dsBIT fields[3]. 
还有一个内存映射文件句柄和偏移量。我们不去理睬最后两个字段。 
因此,使用GDI函数对CreateDIBSection()返回的HBITMAP作图是没有什么问题的。 
 
关于CreateDIBSection与CreateDIBitmap函数的对比可参见:http://blog.csdn.net/strikebone/article/details/5832631
 
一、CreateDIBitmap
 
函数功能:该函数由与设备无关的位图(DIB)创建与设备有关的位图(DDB),并且有选择地为位图置位。
所以此函数最终创建的是与设备有关的位图
原型

HBITMAP CreateDIBitmap(HDC hdc,

CONST BITMAPINFOHEADER *lpbmih,

DWORD fdwlnit,

CONST VOID *lpblnit,

CONST BITMAPINFO *lpbmi,

UINT fuUsage);

参数
hdc:设备环境句柄。由于是设备相关的,所以需要指明DC。
lpbmih:指向位图信息头结构的指针,它可以是下列操作系统位图信息头之一:
BITMAPINFOHEADER  Windows NT 3.51和早期使用。
BITMAPV4HEADER      Windows NT 4.0和Windows 95使用。
BITMAPV5HEADER      Windows NT 5.0和Windows 98使用。
如果fdwlnit是CBM_INIT,那么此函数使用此信息息头结构来获取位图所需的宽度、高度以及其他信息。注意高度若是正数,那么表示是自底向上DIB,而负数表示为自顶向下DIB,这种情况与CreateDIBitmap函数兼容。
Fdwlnit:位标识集。它指定系统如何对位图的位进行初始化。
CBM_INIT:如果设置了该标志,那么系统将使用lpblnit和lpbmi两个参数指向的数据来对位图中的位进行初始化。如果没有该标志,那么表示上述两个参数指向的数据无效。如果fdwlnit为0,那么系统不会对位图的位进行初始化。
lpblnit:该指针指向包含初始的位图数据的字节类型数组。数据格式与参数lpbmi指向的BITMAPINFO结构中的成员biBitCount有关。
lpbmi:指向BITMAPINFO结构的旨针。该结构描述了参数lpbmi指向的数组的维数和颜色格式。
fuUsage:表示BITMAPINFO结构的成员bmiColors是否初始化过,并且如果是,那么bmiColors是否包含明确的红、绿、蓝(RGB)值或调色板索引。参数fuUsage必须取下列值中的一个,这些值的含义为:
DIB_PAL_COLORS :表示提供一个颜色表,并且该表由该位图要选入的设备环境的逻辑调色板的16位索引值数组组成。
DIB_RGB_COLORS:表示提供一个颜色表,并且表中包含了原义的RGB值。
 
返回值
如果函数执行成功,返回值则是创建的位图的句柄;如果函数执行失败,那么返回值为NULL,若想获取更多错误信息,请调用GetLastError函数。
 
实例:以下代码完成从位图文件中读取DIB位图,并转换成DDB位图:
HBITMAP LoadBitmapEx(LPCTSTR lpszFile)
{
if(lpszFile == NULL) return NULL;


HBITMAP hBitmap;
HANDLE hf;
BITMAPFILEHEADER* pbmfh;
DWORD dwBytesRead, dwFileSize, dwFileSizeHigh;
BOOL bSuccess;
// 打开一个bmp文件
hf = CreateFile(lpszFile, GENERIC_READ, FILE_SHARE_READ,
NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL);
if( hf == INVALID_HANDLE_VALUE)
{
TRACE("Open file filed with error %d ", GetLastError());
return NULL;
}
// 得到这个文件大小
dwFileSize = GetFileSize(hf, &dwFileSizeHigh);
if( dwFileSizeHigh )
{
CloseHandle(hf);
return NULL;
}
// 分配内存,大小为该文件的大小
pbmfh = (BITMAPFILEHEADER*)malloc(dwFileSize);
if( !pbmfh )
{
CloseHandle(hf);
return NULL;
}
// 读取数据
bSuccess = ReadFile(hf, pbmfh, dwFileSize, &dwBytesRead, NULL);
CloseHandle(hf);
// 效验文件大小和文件格式
if( !bSuccess || dwFileSize != dwBytesRead || pbmfh->bfType != 0x4D42 || pbmfh->bfSize != dwFileSize)
{
free((void*)pbmfh);
return NULL;
}
// 进行DIB转换
hBitmap = CreateDIBitmap(GetWindowDC(NULL),
(BITMAPINFOHEADER*)(pbmfh + 1),
CBM_INIT,
(BYTE*)pbmfh + pbmfh->bfOffBits,
(BITMAPINFO*)(pbmfh + 1),
DIB_RGB_COLORS);
free((void*)pbmfh);
return hBitmap;
}

  

其中使用到了位图信息头结构BITMAPFILEHEADER)及位图信息结构BITMAPINFO)。
两者的关系详见《BITMAPFILEHEADER 与 BITMAPINFO》。
 
再看一个使用CreateDIBitmap函数如何由裸像素数据得到位图句柄 - HBITMAP:
HBITMAP MakeBitmap(HDC hDc, LPBYTE lpBits, long lWidth, long lHeight, WORD wBitCount)
{
BITMAPINFO bitinfo;
memset(&bitinfo, 0, sizeof(BITMAPINFO));

bitinfo.bmiHeader.biSize          = sizeof(BITMAPINFOHEADER);
bitinfo.bmiHeader.biWidth         = lWidth;     
bitinfo.bmiHeader.biHeight        = lHeight;   
bitinfo.bmiHeader.biPlanes        = 1;  
bitinfo.bmiHeader.biBitCount      = wBitCount;   
bitinfo.bmiHeader.biCompression   = BI_RGB;   
bitinfo.bmiHeader.biSizeImage     = lWidth*lHeight*(wBitCount/8);  
bitinfo.bmiHeader.biXPelsPerMeter = 96;
bitinfo.bmiHeader.biYPelsPerMeter = 96;   
bitinfo.bmiHeader.biClrUsed       = 0;   
bitinfo.bmiHeader.biClrImportant  = 0;
  
return CreateDIBitmap(hDc, &bitinfo.bmiHeader, CBM_INIT, lpBits, &bitinfo, DIB_RGB_COLORS);
}
 
一、CreateDIBSection
函数功能该函数创建应用程序可以直接写入的、与设备无关的位图(DIB)。该函数返回一个位图句柄。

 

函数原型    

HBITMAP CreateDIBSection(HDC                  hdc,
        CONST BITMAPINFO *   pbmi,
        UINT                 iUsage,
        VOID *               ppvBits,
        HANDLE               hSection,
        DWORD                dwOffset

);

参数   

hdc:设备环境句柄。如果iUsage的值是DIB_PAL_COLORS,那么函数使用该设备环境的逻辑调色板对与设备无关位图的颜色进行初始化。

pbmi:指向BITMAPINFO结构的指针,该结构指定了设备无关位图的各种属性,其中包括位图的尺寸和颜色。

iUsage:指定由pbmi参数指定的BITMAPINFO结构中的成员bmiColors数组包含的数据类型(要么是逻辑调色板索引值,要么是原文的RGB值)。下列值是系统定义的,其含义为:

描述

DIB_RGB_COLORS

根据BITMAPINFOHEADER 的biCompression 成员决定BITMAPINFO 结构包含位掩码还是调色板数组,在呈现位图时使用该数组值。DIB_RGB_COLORS 可以在任何位数的位图上使用。

DIB_PAL_COLORS

BITMAPINFO.bmiColors 数组被取消,在呈现位图时使用目标调色板。DIB_PAL_COLORS只能在8bpp位图中指定。

ppvBits:指向一个变量的指针,该变量接收一个指向DIB位数据值的指针。

hSection:该参数设置为NULL。

dwOffset:参数取消。

返回值

   成功,返回值是一个指向刚刚创建的设备无关位图的句柄,并且*ppvBits指向该位图的位数据值;失败,那么返回值为NULL,并且*ppvBit也为NULL,若想获得更多错误信息,请调用GetLastError函数。

 

备注:

   系统为设备独立位图分配内存。如果在之后调用DeleteObject来删除设备独立位图,系统自动关闭内句柄。

   在Windows CE 2.0及其以后版本,如果图像是调色板模式(通常是1,2,4和8格式)的,BITMAPINFO 结构中必须包含一个颜色表。对于16bpp或32bpp非调色板图像,颜色表必须是3个入口的长度,这3个入口必须指定红、绿、蓝色掩码。 而且,BITMAPINFOHEADER 结构的biCompression 成员应该被设置为BI_BITFIELDS。 这些位深不支持BI_RGB。 GDI取消24bpp图像的颜色表,他们的像素必须被存储为 蓝-绿-红 (BGR)格式。

   Windows CE 不支持332位阈设备。

   在Windows CE 1.0 和 1.01版本,pbmi指向的BITMAPINFO结构必须指定每个像素点为1或2位。

 

实例:下面这段代码实现由已存在的图像裸数据得到CBitmap位图:

CBitmap bitmap ;
BITMAPINFO bmpInfo; //创建位图 
bmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmpInfo.bmiHeader.biWidth = 480;//宽度
bmpInfo.bmiHeader.biHeight = 480;//高度
bmpInfo.bmiHeader.biPlanes = 1;
bmpInfo.bmiHeader.biBitCount = 24;
bmpInfo.bmiHeader.biCompression = BI_RGB;

UINT uiTotalBytes = 480 * 480 * 3;
void *pArray = new BYTE(uiTotalBytes );
HBITMAP hbmp = CreateDIBSection(NULL, &bmpInfo, DIB_RGB_COLORS, &pArray, NULL, 0);//创建DIB
ASSERT(hbmp != NULL);
//! 将裸数据复制到bitmap关联的像素区域
memcpy(pArray, pImageData, uiTotalBytes);

bitmap.Attach(hbmp);
其中pImageData即是已知的图像裸数据。

注意

 

这里想说声sorry,之前没太深入理解CreateDIBSection函数的工作机理,在上面的例子中使用new为pArray开辟了一块空间,然后将pArray传入CreateDIBSection函数,其实这种方式是错误的,在Debug下面调试之后发现,你使用new开辟的pArray,

 

在使用之后并不能正常delete[]掉。CreateDIBSection参数ppvBits正常的使用应该只要传入一个指针即可(就像上面实例中那样

 

,只要定义一个BYTE *pArray,不需要自己分配空间,直接传给CreateDIBSection即可)。

 

其实通过ppvBits参数类型(二级指针)也就明白了CreateDIBSection函数的基本使用原理 - CreateDIBSection函数使用hDC及

 

BITMAPINFO结构信息创建一个指定大小等信息的位图,系统自动为其开辟所需的像素空间,开辟的像素空间地址就是用ppvBits参数

 

返回的。而且此位图空间系统会自动释放。

 

之所以需要返回此地址,是因为CreateDIBSection调用时只是根据我们提供的一些信息创建合适大小、格式的位图并开辟控件

 

,而并不会填充实际像素值(这也是为什么我们直接将指向真实像素值的指针传入此函数中无效的原因),返回这个指针只是为了

 

之后使用真实像素值填充此段空间(就像上面示例中的memcpy那样)!

 

 

1、关于CreateDIBSection函数的使用需要注意一点就是参数ppvBits的使用:传给CreateDIBSection函数的不是一个new

 

来一块的空间,也不是直接传入真实像素指针pImageData,而只是传入一个指针变量(如本例中的pArray)用以接收CreateDIBSection自动开辟的像素区域指针!如果你传入了自己手动new出来的一块区域,在Debug调试运行时会发现一个delete []错误,此错误指明你之前new出来的空间无法释放,原因是堆损坏,这部分需要了解下new、delete(或内存检测)原理,详见《new/delete内存分配及释放检测》。

 

2、像素数据的复制必须在调用了CreateDIBSection函数之后进行,如本例所示,memcpy(pArray, pImageData, uiTotalBytes);是在HBITMAP hbmp = CreateDIBSection(NULL, &bmpInfo, DIB_RGB_COLORS, &pArray, NULL, 0);之后进行的。可能是由于CreateDIBSection函数会对传入的像素空间进行“清除”工作,所以在调用此函数前就先进行像素复制的话,你会发现最终绘制的图像时一片漆黑!

 

说明:CreateDIBSection函数具有内存映射文件功能,但是一般我们不会使用!所以此处忽略讨论!

 

此外还需说明的是,有时候在使用CreateDIBSection函数时会发生内存泄露的问题。当初本人开发一个项目时曾经碰到过此问题,在一个窗口进入特定模式后会频繁的调用此函数生成特定的图像,并用此图像去重绘窗口背景。最后调试正是此函数发生了内存泄露,但是代码中确是对产生的HBITMAP及DC进行了释放。当时是使用了另一种方式解决了此问题,但是对于CreateDIBSection函数发生的泄露问题却一直没有深入研究。

 

虽然如此,对于使用此函数之后善后处理这里仅提几点:

1、最后一次使用完CreateDIBSection函数创建位图之后将其从DC中换出memdc.SelectObject(pOldBmp);

2、在最后一次使用完CreateDIBSection函数创建的位图之后才可delete[] pArray释放掉像素空间。

3、注意释放位图资源bitmap.DeleteObject();

4、释放内存memdc.ReleaseDC();

 

更多关于CreateDIBSection函数内存使用问题详见:http://blog.sina.com.cn/s/blog_8ec096580101gmmb.html

 

 

posted @ 2013-08-17 17:39  执迷不悟~  阅读(20687)  评论(3编辑  收藏  举报