Windows 位图

 

1简介    1

1.1 DFB    1

1.2 DDB    1

1.3 DIB    2

2相关API    3

2.1 创建    3

2.1.1 CreateCompatibleBitmap    3

2.1.2 CreateBitmap    3

2.1.3 CreateBitmapIndirect    4

2.1.4 CreateDIBitmap    4

2.1.5 CreateDIBSection    5

2.1.6 小结    6

2.2 查询    6

2.3 载入DIB    6

2.4 访问位图数据    7

2.5 位图传输    7

3示例代码    8

3.1 屏幕拷贝    8

3.1.1 CaptureScreen    10

3.1.2 BmpToClipboard    11

3.1.3 BmpToFile    11

3.2 读取位图文件并显示    12

 

 

1简介

下图显示了三种位图:DFBDDBDIB

图1.1

1.1 DFB

DFBDevice-format bitmap)是设备格式位图。假如设备是显示器,那么DFB就在显卡内存中。DFB格式并没有统一的标准,所以直接访问DFB并不是一个明智的做法。事实上,在Windows系统里一般是无法直接访问DFB的,程序员常常使用设备的HDC与之联系,如下面的三行代码将在屏幕上绘制一个矩形,其实质就是通过屏幕的HDC访问DFB,往显存里绘制了一个矩形。

HDC hDC = GetDC(NULL);            //获得屏幕的 HDC

Rectangle(hDC,200,100,50,90);            //绘制一个矩形

ReleaseDC(NULL,hDC);                    //释放 HDC

1.2 DDB

DDBDevice-dependent bitmap)是设备相关位图,其实就是HBITMAP。笔者认为它存在的理由之一就是:通过HDC访问DFB的效率有时太低了。它叫设备相关位图是因为它的内部格式与DFB是有关系的,保证了与DFB相互转换、传输时的高效率。

访问DDB的方法有三种:

1、通过内存HDC

2、分配内存,通过GetDIBits获取位图数据,通过SetDIBits将修改后的位图数据写入HBITMAP

3、创建DDB时获取位图数据的首地址,这样就可以直接访问位图了。不过这种方法创建的位图实质上已经不是DDB了,它不再与DFB有关联,相互转换时不能保证最高的效率。

DDBDFB可通过BitBlt等函数相互转换、传输。

1.3 DIB

DDB的格式随DFB的格式而变,如果需要根据不同的DDB格式编写不同的代码去操纵一个像素,那将是程序员的恶梦。这个时候就需要DIB了。

DIBDevice-independent bitmap)是设备无关位图,是程序员访问DDB时的位图标准格式。知道了这个格式就可以对位图中的单个像素进行高效的操作了。DIB位图读写完毕后,可通过API函数将其转换为DDB

DIB的实质就是一种位图格式,而且它基本上就是.bmp文件格式。所以,把DIB理解为加载进内存中的.bmp文件也是可以的。

DIB通过GetDIBitsSetDIBits访问DDB

DIB通过SetDIBitsToDevice等函数可直接访问DFB。因为格式不同,所以需要大量的转换,不能保证高效性。

 

2相关API

2.1 创建

创建位图的API5个:

CreateBitmap

CreateBitmapIndirect

CreateCompatibleBitmap

CreateDIBitmap

CreateDIBSection

下面逐个介绍

2.1.1 CreateCompatibleBitmap

使用示例:

HDC        hDC    =    GetDC(NULL);

HBITMAP    hBmp    =    CreateCompatibleBitmap(hDC,100,50);

第一行代码获取屏幕的 hDC

第二行代码将创建与屏幕兼容的DDB位图,其大小为100×50

hBmphDC兼容的含义就是其格式尽量与hDCDFB保持一致,这样在与DFB相互转换、传输时效率就会比较高。

2.1.2 CreateBitmap

此函数的声明如下:

HBITMAP CreateBitmap(int nWidth,                //位图宽,单位:像素

                        int nHeight,                    //位图高,单位:像素

                        UINT cPlanes,                //位图颜色面数

                        UINT cBitsPerPel,            //一个像素的比特数

                        CONST VOID *lpvBits);    //对像素进行初始化

它的实质就是创建了一个三维数组。数组大小为:nWidtnHeighcPlanes,数组中的每个元素代表一个像素,每个像素占用的比特数为cBitsPerPel

如果要对三维数组进行初始化,请指定参数lpvBits

现在再来看看上一节的代码:

HBITMAP    hBmp    =    CreateCompatibleBitmap(hDC,100,50);

它可以被CreateBitmap函数代替,具体代码如下:

HBITMAP    hBmp    =    CreateBitmap(100,50

                            ,GetDeviceCaps(hDC,PLANES)

                            ,GetDeviceCaps(hDC,BITSPIXEL)

                            ,NULL);

2.1.3 CreateBitmapIndirect

CreateBitmapIndirectCreateBitmap的简化版本,如下面的代码:

BITMAP bm;

CreateBitmapIndirect(&bm);

等价于下面的代码:

CreateBitmap(bm.bmWidth,bm.bmHeight

                ,bm.bmPlanes,bm.bmBitsPixel,bm.bmBits);

2.1.4 CreateDIBitmap

CreateDIBitmap不是创建一个DIB位图,而是创建一个DDB位图,然后用DIB位图数据对这个DDB位图进行初始化。其声明如下:

HBITMAP CreateDIBitmap(HDC hdc,

    CONST BITMAPINFOHEADER *lpbmih,    //指定位图的宽和高

    DWORD fdwInit,                            //是否初始化

    CONST VOID *lpbInit,                        //DIB像素数据首地址

    CONST BITMAPINFO *lpbmi,                //DIB信息及颜色表)

    UINT fuUsage);                                //DIB_RGB_COLORS

它等效于如下代码,即首先创建DDB位图,然后再根据DIB位图数据对其进行初始化。

HBITMAP hBmp = CreateCompatibleBitmap(hDC

                                ,lpbmi->biWidth,labs(lpbmi->biHeight));

if(CBM_INIT==fdwInit)

{

SetDIBits(hDC,hBmp,0,labs(lpbmi->biHeight),lpbInit,lpbmi,fuUsage);

}

2.1.5 CreateDIBSection

以上创建的位图都是DDB位图,CreateDIBSection创建的将是一个DIB位图,其声明如下:

HBITMAP CreateDIBSection(

HDC hdc, // handle to device context

CONST BITMAPINFO *pbmi,

// pointer to structure containing bitmap size,

// format, and color data

UINT iUsage, // color data type indicator: RGB values or

// palette indexes

VOID **ppvBits, // pointer to variable to receive a pointer to

// the bitmap's bit values

HANDLE hSection, // optional handle to a file mapping object

DWORD dwOffset // offset to the bitmap bit values within the

// file mapping object

);

1个参数hdc一般没什么用,仅当iUsageDIB_PAL_COLORS时才会用到它;

2个参数pbmi指定了DIB位图的宽度、高度、颜色位及颜色表;

3个参数iUsage说明了颜色表中的颜色含义。iUsageDIB_RGB_COLORS时,颜色表中的颜色是RGB格式;iUsageDIB_PAL_COLORS时,颜色表中的颜色是索引值,这个时候就需要用第1个参数hdc将索引值转换为RGB值;

4个参数ppvBits是一个输出参数,*ppvBits是一个void*,它指向了DIB像素数据的首地址。也就是说CreateDIBSection会为DIB位图分配内存,然后把像素数据首地址通过ppvBits传出来,程序员根据此地址即可对DIB位图进行操作。

最后两个参数一般不用,直接设置为NULL0即可。

2.1.6 小结

CreateCompatibleBitmap是最简单且经常用到的;

CreateBitmap是最正规的;

CreateBitmapIndirectCreateBitmap的简化版;

CreateDIBitmap在创建DDB位图后,用DIB位图进行初始化;

CreateDIBSection最特殊,它创建的不是DDB,而是DIB。也只有它能获得位图数据的地址,能够对位图数据进行直接操作。

2.2 查询

给定一个HBITMAP hBmp,如何得知它的宽度、高度等信息?答案就是使用GetObject函数,其示例代码如下:

BITMAP bm;

GetObject(hBmp,sizeof(bm),&bm);

此时:

bm.bmWidth;         //位图宽度,单位:像素

bm.bmHeight;         //位图高度,单位:像素。如果是DIB,它可能为负

bm.bmWidthBytes;     //位图一行的字节数

bm.bmPlanes;         //位图颜色面数

bm.bmBitsPixel;     //每个像素的比特数

bm.bmBits;            //如果是DIB,它就是位图数据的地址,否则为NULL

只有通过CreateDIBSection获得的HBITMAP,它对应的bm.bmBits才不为NULL。此时,下面的代码将获得更加详细的信息:

DIBSECTION ds;

GetObject(hBmp,sizeof(ds),&ds);

DIBSECTION 的定义请参考 MSDN

2.3 载入DIB

LoadBitmap        从资源里载入位图

LoadImage        从资源或文件里载入位图

2.4 访问位图数据

GetBitmapBitsSetBitmapBits可用于访问位图中的像素数据。这两个函数不能获得颜色表,属于过时的函数。

GetDIBitsSetDIBitsGetBitmapBitsSetBitmapBits的升级版本,它们增加了处理颜色表的功能。

2.5 位图传输

位图传输主要有如下几个函数

BitBlt

MaskBlt

PlgBlt

StretchBlt

TransparentBlt

上面的函数StretchBlt,可以用SetStretchBltMode设置模式,用GetStretchBltMode获取模式。

上面几个函数都是在两个HDC之间传输位图,下面两个函数是将DIB位图直接传输到HDC上:

SetDIBitsToDevice

StretchDIBits

 

3示例代码

3.1 屏幕拷贝

Windows系统里,按下 Print Screen 键即可将屏幕位图拷贝到剪贴板中。下面是实现此功能的 MFC 代码:

HBITMAP CaptureScreen()

{

int    cx            =    GetSystemMetrics(SM_CXSCREEN);

int    cy            =    GetSystemMetrics(SM_CYSCREEN);

HDC        hDC        =    GetDC(NULL);

HBITMAP    hBmp        =    CreateCompatibleBitmap(hDC,cx,cy);

HDC        hMem        =    CreateCompatibleDC(hDC);

HGDIOBJ    hBmpOld    =    SelectObject(hMem,hBmp);

BitBlt(hMem,0,0,cx,cy,hDC,0,0,SRCCOPY);

SelectObject(hMem,hBmpOld);

DeleteDC(hMem);

ReleaseDC(NULL,hDC);

return hBmp;

}

 

void BmpToClipboard(HBITMAP hBmp)

{

if(OpenClipboard(NULL))

{

EmptyClipboard();

SetClipboardData(CF_BITMAP,hBmp);

CloseClipboard();

}

}

 

void BmpToFile(HBITMAP hBmp,WORD wBitsPixel,LPCTSTR szFile)

{

BITMAP bm;

GetObject(hBmp,sizeof(bm),&bm);

if(0 == wBitsPixel)

{

wBitsPixel = bm.bmBitsPixel;

}

DWORD dwSizeClr = 0; //颜色表字节数

if(wBitsPixel <= 8)

{

dwSizeClr = (1 << wBitsPixel) * sizeof(RGBQUAD);

}

//一行的字节数

DWORD dwLineBytes = ((bm.bmWidth * wBitsPixel + 31) & ~31) >> 3;

//像素数据字节数

DWORD dwSizeImg = dwLineBytes * bm.bmHeight;

BITMAPFILEHEADER bmfh;

bmfh.bfType = 0x4D42; //BM

bmfh.bfOffBits = sizeof(bmfh) + sizeof(BITMAPINFOHEADER) + dwSizeClr;

bmfh.bfSize = bmfh.bfOffBits + dwSizeImg;

bmfh.bfReserved1 = 0;

bmfh.bfReserved2 = 0;

BYTE* pBmpFile = (BYTE*)malloc(bmfh.bfSize);

memcpy(pBmpFile,&bmfh,sizeof(bmfh));

LPBITMAPINFO lpbi = (LPBITMAPINFO)(pBmpFile + sizeof(bmfh));

lpbi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);

lpbi->bmiHeader.biWidth = bm.bmWidth;

lpbi->bmiHeader.biHeight = bm.bmHeight;

lpbi->bmiHeader.biPlanes = 1;

lpbi->bmiHeader.biBitCount = wBitsPixel;

lpbi->bmiHeader.biCompression = BI_RGB;

lpbi->bmiHeader.biSizeImage = dwSizeImg;

lpbi->bmiHeader.biXPelsPerMeter = 0;

lpbi->bmiHeader.biYPelsPerMeter = 0;

lpbi->bmiHeader.biClrUsed = 0;

lpbi->bmiHeader.biClrImportant = 0;

HDC hDC = GetDC(NULL);

GetDIBits(hDC,hBmp,0,bm.bmHeight

,pBmpFile + bmfh.bfOffBits,lpbi,DIB_RGB_COLORS);

ReleaseDC(NULL,hDC);

CFile f;

f.Open(szFile,CFile::modeWrite | CFile::modeCreate);

f.Write(pBmpFile,bmfh.bfSize);

f.Close();

free(pBmpFile);

}

 

void CTestDlg::OnButton3()

{

HBITMAP hBmp = CaptureScreen();

if(hBmp)

{

BmpToClipboard(hBmp);

BmpToFile(hBmp, 0,_T("0.bmp"));

BmpToFile(hBmp, 1,_T("1.bmp"));

BmpToFile(hBmp, 4,_T("4.bmp"));

BmpToFile(hBmp, 8,_T("8.bmp"));

BmpToFile(hBmp,16,_T("16.bmp"));

BmpToFile(hBmp,24,_T("24.bmp"));

BmpToFile(hBmp,32,_T("32.bmp"));

DeleteObject(hBmp);

}

}

当用户单击Button3时,会调用CTestDlg::OnButton3。它做了三项工作:

1、调用CaptureScreen(),将屏幕的DFB转换为DDB

2、调用BmpToClipboard(hBmp);DDB复制到剪贴板里;

3、调用BmpToFile(hBmp, 0,_T("0.bmp"));DDB保存到文件里。

下面逐个进行介绍。

3.1.1 CaptureScreen

屏幕拷贝的实质就是将显存里的DFB提取出来,转换为DDB

根据图1.1可知,DFBDDB可通过函数BitBlt相互转换、传输。

现在的问题是:BitBlt只能在两个HDC之间相互传输位图,并不能直接操纵HBITMAP。这里就需要内存HDC

CaptureScreen的详细讲解如下:

//下面两行代码用来获得屏幕的宽度和高度,单位是像素。

int cx = GetSystemMetrics(SM_CXSCREEN);

int cy = GetSystemMetrics(SM_CYSCREEN);

//下面这行代码用来获得屏幕的HDC

HDC hDC = GetDC(NULL);

//创建与屏幕兼容的DDB

HBITMAP hBmp = CreateCompatibleBitmap(hDC,cx,cy);

//创建与屏幕兼容的内存HDC

HDC hMem = CreateCompatibleDC(hDC);

//DDB选入内存HDC

HGDIOBJ hBmpOld = SelectObject(hMem,hBmp);

//DFB传输给DDB

BitBlt(hMem,0,0,cx,cy,hDC,0,0,SRCCOPY);

//恢复内存HDC原来的位图

SelectObject(hMem,hBmpOld);

//销毁内存HDC

DeleteDC(hMem);

//释放屏幕HDC

ReleaseDC(NULL,hDC);

3.1.2 BmpToClipboard

BmpToClipboard函数把DDB复制到系统剪贴板内,代码很简单,不用多解释。

3.1.3 BmpToFile

BmpToFile函数用来把DDB转换为DIB,然后把DIB存入文件里。它的参数如下所示:

void BmpToFile(HBITMAP hBmp,WORD wBitsPixel,LPCTSTR szFile)

第一个参数是DDB

第二个参数是转换为DIB时的颜色位数。0 表示采用DDB的颜色位数,其它的取值有:1248162432。颜色位数越大,颜色数就越多,生成的文件越大;

第三个参数是保存时的文件名。

BmpToFile函数的关键点有:

1、调用GetDIBits,把DDB的位图数据以DIB的格式提取出来;

2、调用f.Write(pBmpFile,bmfh.bfSize);DIB数据写入文件。下面就是.bmp文件的格式:

图3.1

首先是一个BITMAPFILEHEADERpBmpFile指向它;

接着是一个BITMAPINFOHEADERlpbi指向它;BITMAPINFO的第一个成员变量就是BITMAPINFOHEADER型的,所以也可以把lpbi看做BITMAPINFO*

对于颜色位数小于16DIB需要定义颜色表,即定义多个RGBQUAD。对于颜色位数大于等于16DIB,一般是不需要RGBQUAD的。

lpbi->bmiColors指向颜色表。颜色表的项数为(1 << wBitsPixel),颜色表的字节数为(1 << wBitsPixel) * sizeof(RGBQUAD);

中间还有一个"未定义",它可以是零字节也可以是多个字节,内容随便填。

最后是像素数据,pBmpFile + bmfh.bfOffBits指向它。

现在再看看GetDIBits的代码:

GetDIBits(hDC,hBmp,0,bm.bmHeight

,pBmpFile + bmfh.bfOffBits,lpbi,DIB_RGB_COLORS);

5个参数pBmpFile + bmfh.bfOffBits指向像素数据;

6个参数lpbi是一个BITMAPINFO*lpbi->bmiColors指向颜色表。

调用GetDIBits将改写颜色表(lpbi->bmiColors)和像素数据(pBmpFile + bmfh.bfOffBits)。

3.2 读取位图文件并显示

读取位图文件并显示的代码有两份。

第一份代码如下所示:

void CTestDlg::OnButton4()

{

HBITMAP hBmp = (HBITMAP)LoadImage(NULL,_T("0.bmp")

,IMAGE_BITMAP,0,0,LR_DEFAULTSIZE | LR_LOADFROMFILE);

if(hBmp)

{

BITMAP bm;

GetObject(hBmp,sizeof(bm),&bm);

CClientDC dc(this);

HDC hMem = CreateCompatibleDC(dc.m_hDC);

HGDIOBJ hBmpOld = SelectObject(hMem,hBmp);

::BitBlt(dc.m_hDC,0,0,bm.bmWidth,bm.bmHeight,hMem,0,0,SRCCOPY);

SelectObject(hMem,hBmpOld);

DeleteDC(hMem);

DeleteObject(hBmp);

}

}

说明:

1、载入位图使用了LoadImage函数。

2、显示位图的步骤:

1)创建一个与屏幕兼容的内存HDC

2)将载入的位图选入内存HDC

3)调用BitBlt将内存HDC的内容传输到屏幕HDC上;

4)销毁内存HDC

第二份代码如下所示:

void CTestDlg::OnButton5()

{

CFile f;

if(f.Open(_T("1.bmp"),CFile::modeRead))

{

DWORD dwSize = f.GetLength();

if(dwSize > sizeof(BITMAPFILEHEADER)

+ sizeof(BITMAPINFOHEADER))

{

BYTE* pBmpFile = (BYTE*)malloc(dwSize);

f.Read(pBmpFile,dwSize);

BITMAPFILEHEADER*pbmfh =

(BITMAPFILEHEADER*)pBmpFile;

if(pbmfh->bfType == 0x4D42)

{

BITMAPINFOHEADER*pbmih =

(BITMAPINFOHEADER*)(pBmpFile + sizeof(BITMAPFILEHEADER));

CClientDC dc(this);

SetDIBitsToDevice(dc.m_hDC

,0,0,pbmih->biWidth,pbmih->biHeight

,0,0,0,pbmih->biHeight

,pBmpFile + pbmfh->bfOffBits

,(BITMAPINFO*)pbmih

,DIB_RGB_COLORS);

}

free(pBmpFile);

}

f.Close();

}

}

说明:

1、载入位图就是打开位图文件,然后将所有内容载入到内存;

2、显示位图没有使用BitBlt,而是使用SetDIBitsToDevice直接把DIB传输到屏幕。其中pBmpFile + pbmfh->bfOffBits指向像素数据,(BITMAPINFO*)pbmih指向BITMAPINFOpbmih->bmiColors指向颜色表。

 

posted @ 2016-12-12 14:45  hanford  阅读(1417)  评论(1编辑  收藏  举报