关于GetDIBits的使用 抓狂搞了1整天 备注待查

 

HBITMAP是常用的GDI对象,而GetDIBits可以从一个HBITMAP对象中获得其对应的位数据。
其原型如下:
int GetDIBits(    HDC hdc, // handle to DC
hdc, // handle to DC
                            HBITMAP hbmp, // handle to bitmap
                            UINT hbmp, // handle to bitmap
                            UINT uStartScan, // first scan line to set
                            UINT cScanLines, // number of scan lines to copy
                            LPVOID cScanLines, // number of scan lines to copy
                            LPVOID lpvBits, // array for bitmap bits
                            LPBITMAPINFO lpbi, // bitmap data buffer
                            UINT lpbi, // bitmap data buffer
                            UINT uUsage // RGB or palette index );

标准的GetDIBits调用方式是两次调用:
第一次传入空的 lpvBits,此时的lpbi作为传出参数,从中可以获得lpvBits所需的内存区域大小。
    BYTE *lpvBits = NULL;
    BITMAPINFO bmpInfo = {0};
    bmpInfo.bmiHeader.biSize = sizeof(bmpInfo.bmiHeader);

    /*    第一次调用GetDIBits获得bmpInfo    */
    nRet = ::GetDIBits(hDC, hBitmap, 0, pBmpData->bmHeight, NULL, &bmpInfo, DIB_RGB_COLORS);
    if (nRet == 0) {
        nRet = GetLastError();
        TRACE( _T("GetDIBits for bmpInfo failed %d/n"), nRet);
        goto err;
    }

以上调用如果成功,就会在bmpInfo.bmiHeader.biSizeImage记录位数据所占的内存区大小,
此时就可以动态分配内存,然后再次调用GetDIBits获得实际的位数据:

    lpvBits= new BYTE[bmpInfo.bmiHeader.biSizeImage];
    if (NULL == pBitsBuffer) {
        nRet = -1;
        TRACE( _T("Allocate memory for lpvBits failed/n"));
        goto err;
    }

    /*    第二次调用GetDIBits获得位图数据    */
    nRet = ::GetDIBits(hDC, hBitmap, 0, pBmpData->bmHeight, lpvBits, &bmpInfo, DIB_RGB_COLORS);
    if (nRet == 0) {
        nRet = GetLastError();
        TRACE( _T("GetDIBits for lpvBits failed %d/n"), nRet);
        goto err;
    }

很多时候,因为位图的长宽和颜色深度都是已知的,因此位数据所占的内存区大小可以由公式
 width * heigth * pixelBits / 8 计算获得

那是否可以省略第一步调用呢?
答案是:最好不要

原因是 bmpInfo.bmiHeader.biSizeImage 并不一定等于 width * heigth * pixelBits / 8

经过多次测试发现,对于16位颜色深度的位图,如果其宽度为奇数,则第一次GetDIBits获得
的位数据大小为 (width+1) * heigth * pixelBits / 8

也就是说对于16位颜色深度的位图,HBITMAP对象对其进行存储时,自动将宽度调整为了偶
数,也即将每行数据对齐到4字节(一个DWORD的长度)。

暂时没有查到相关的解释,我的猜想是这是为了在进行像素操作时能直接以DWORD为单位,
这样比以WORD为单位能减少一半的操作次数。

 

 

16位操作系统下如果使用这种方法获得的将是16位图

biBitCount=16 

表示位图最多有2^16种颜色。每个像素用16位(2个字节)表示。这种格式叫作高彩色,或叫增强型16位色,或64K色。它的情况比较复杂,当biCompression成员的值是BI_RGB时,它没有调色板。16位中,最低的5位表示蓝色分量,中间的5位表示绿色分量,高的5位表示红色分量,一共占用了15位,最高的一位保留,设为0。//->备注1

这种格式也被称作555 16位位图。

内存分布如下


如果biCompression成员的值是BI_BITFIELDS(const DWORD BI_BITFIELDS = 3;)那么情况就复杂了,首先是原来调色板的位置被三个DWORD变量占据,称为红、绿、蓝掩码。分别用于描述红、绿、蓝分量在16位中所占的位置。在Windows 95(或98)中,系统可接受两种格式的位域:555565,在555格式下,红、绿、蓝的掩码分别是:0x7C000x03E00x001F,而在565格式下,它们则分别为:0xF8000x07E00x001F。你在读取一个像素之后,可以分别用掩码上像素值,

 

从而提取出想要的颜色分量(当然还要再经过适当的左右移操作)。在NT系统中,则没有格式限制,只不过要求掩码之间不能有重叠。(注:这种格式的图像使用起来是比较麻烦的,不过因为它的显示效果接近于真彩,而图像数据又比真彩图像小的多,所以,它更多的被用于游戏软件)。我们只需要读取其中的R或者G的掩码,来判断是那种格式。以红色掩码为例 0111110000000000的时候就是555格式 1111100000000000就是565格式。

555 格式 xrrrrrgggggbbbbb

565 格式 rrrrrggggggbbbbb

解析555格式的代码:

BYTE b= bmpData[i*storeWidth+j];

BYTE g=((bmpData[i*storeWidth+j +1]<<6)>>3)+(buffer[i*storeWidth+j]>>5);

BYTE r=(bmpData[i*storeWidth+j +1]<<1)>>3;

有一点值得提醒的是由于有较多的位操作,所以在处理的时候在前一次操作的上面加上一对括号。

现在我们得到了55RGB各自的分量,但是还有一个新的问题,那就是由于两字节表示了3个颜色  555下每个颜色最多到0x1F。所以我们需要一个转换,这就是掩码的用武之地,将得到的各颜色分量和相应的掩码做与运算,或者乘8就可以了,推荐用与运算。

 

以下是565格式时的数据分离:

BYTE b= bmpData[i*storeWidth+j];

BYTE g=((bmpData[i*storeWidth+j+1]<<5)>>2)+(buffer[i*storeWidth+j]>>5);

BYTE r=bmpData[i*storeWidth+j +1]>>3;

 

现在我们得到了565RGB各自的分量,但是仍然还有一个新的问题,565格式下最大的绿色分量也就0x3F。所以我们需要一个转换,这就是掩码的用武之地,将各颜色分量和相应的掩码做与运算,

 

 

 备注一:

           所以这种方法需要通过位移来操作 在写抓屏程序的时候显示不如后边的32位图来的方便, 但是在颜色质量为16位的操作系统下如何抓屏到32位的图像呢

通过上边的两个GetDIBits的方法是不可行的  第一次LPVOID lpvBits, 为空 获得了BITMAPINFO bi信息bi.bmiHeader.biBitCount = 16;

如果想通过修改bi.bmiHeader.biBitCount = 32; 后来第二次调用GetDIBits就会失败 ;

 

那如何在颜色质量为16位的操作系统下如何获得32位的图像呢?

方法如下:

首先使用 GetObject(HBITMAP bmpScreen) 获得信息

然后通过修改BITMAPINFO   中的biBitCount 为32就可以获得32位结构的LPVOID 了 再进行操作就方便的多了

BITMAPINFO bi = {0}; bi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bi.bmiHeader.biWidth = bmpScreen.bmWidth; bi.bmiHeader.biHeight = bmpScreen.bmHeight; bi.bmiHeader.biPlanes = 1; bi.bmiHeader.biBitCount = 16; bi.bmiHeader.biCompression = BI_RGB; bi.bmiHeader.biSizeImage = 0; bi.bmiHeader.biXPelsPerMeter = 0; bi.bmiHeader.biYPelsPerMeter = 0; bi.bmiHeader.biClrUsed = 0; bi.bmiHeader.biClrImportant = 0;

biBitCount=32 

 

表示位图最多有2^32种颜色。这种位图的结构与16位位图结构非常类似,当biCompression成员的值是BI_RGB时,它也没有调色板,32位中有24位用于存放RGB值,顺序是:最前一字节保留,红8位、绿8位、蓝8位。这种格式也被成为888 32位图。如果 biCompression成员的值是BI_BITFIELDS时,原来调色板的位置将被三个DWORD变量占据,成为红、绿、蓝掩码,分别用于描述红、绿、蓝分量在32位中所占的位置。在Windows 95(or98)中,系统只接受888格式,也就是说三个掩码的值将只能是:0xFF00000xFF000xFF。而在NT系统中,你只要注意使掩码之间不产生重叠就行。

 

 

你可能感兴趣的:(Windows编程,Windows编程)