位图和Bitblt
位图是一个二维的位数组,此数组的每一个元素与图像的像素一一对应。现实世界的图像被捕获以后,图像被分割成网格,并以像素作为取样单位。位图中的每个像素值指明了一个单位网格内图像的平均颜色。
位图代表了Windows程序中存储图像信息的两种方法之一,另一种形式是元文件。
位图也有两种:GDI位图对象和设备无关的位图(DIB: device-independent bitmap)。
位图基础
位图常用来表示来自真实世界的复杂图像,元文件更适合于描述由人或者机器生成的图像。它们都能存于内存或作为文件存于磁盘上,且能通过剪贴板在Windows应用程序间传输。
位图和元文件的区别在于光栅图像和矢量图像间的差别。光栅图像用离散的像素来处理输出设备;矢量图像用笛卡尔坐标系统来处理输出设备,可在其中绘制线和填充对象。
位图的缺点:1、容易受设备依赖性的影响。2、位图常暗示了特定的显示分辨率和图像纵横比,在缩放后容易出现失真。3、存储空间大。
但位图具有处理速度快的优点。
位图可以手工创建,也可计算机代码生成,还可由硬件设备把现实世界输入到计算机,如数码相机,它们通常是使用接触到光就释放电荷的电荷耦合装置(CCD: charge-coupled device)将光的强度转换为电荷,再用模数转换装置(ADC: Analog-to-digital)转换为数字再排列为位图。
位图尺寸
位图呈矩形,具有空间尺寸,以像素为单位度量位图的高度和宽度。以位于图像左上角为位图原点,从0开始计数。
位图的空间尺寸也指定了其分辨率,但此词具有争议,分辨率也指单位长度内的像素数。
位图是矩形的,但内存是线性的。大多数位图按行存储在内存中,且从顶行像素开始从左到右直到底行结束。
位图还有颜色度量单位:指每个像素所需要的位数,也称颜色深度(color depth)、位数(bit-count)、或位/每像素(bpp: bits per pixel)。每个像素用1位来描述的位图称为二级(bilevel)、二色(bicolor)或单色(monochrome)位图。每个像素也可用多位来描述,可以表示的颜色数等于2的i次方(i为位数)。
如何将颜色位的组合与人们所熟识的颜色相对应是处理位图时经常出现的问题。
位块传送
一、BitBlt函数:原样复制
整个视频显示器可看作是一幅大位图,其上的像素由存储在视频显示适配卡上内存中的位来描述。所以,我们可以使用BitBlt函数来完成将图像从视频显示的一个区域复制到另一个区域。这就是位块传送(bit-block tranfer)。此函数是像素移动程序,实际上对像素执行了一次位运算操作。
BitBlt函数从称为“源”的设备描述表中将一个矩形区的像素传送到称为“目标”的另一个设备描述表中相同大小的矩形区。源和目标设备描述表可以相同。此函数语法如下:
BitBlt(hdcDst,xDst,yDst,cx,cy,hdcSrc,xSrc,ySrc,dwROP);
xSrc和ySrc参数指明了源图像左上角在源设备描述表中的坐标位置。cx和cy是图像的宽度和高度。xDst和yDst是图像复制到的设备描述表中的坐标位置。dwROP是光栅操作符。
注意:BitBlt是从实际视频显示内存传送像素,也就是说整个显示屏上的图像都存于显存中,若图像超出了显示屏,那么BitBlt只传送在显示屏上的部分。
BitBlt的最大限制是两个设备描述表必须兼容,就是说两者的每个像素都具有相同的位数。所以,不能用它将屏幕上的某些图形复制到打印机。
二、StretchBlt函数:拉伸位图
此函数语法如下:StretchBlt(hdcDst,xDst,yDst,cxDst,cyDst,
hdcSrc,xSrc,ySrc,cxSrc,cySrc,dwROP);
BitBlt和StretchBlt函数中所有的坐标与大小都是基于逻辑单位的。但如果BitBlt函数中有两个不同的设备描述表,而这两个设备描述表引用相同的物理设备却具有不同的映射模式时,BitBlt就不明确了:cx和cy都是逻辑单位,它们同样应用于源设备描述表和目标设备描述表中的矩形区。此时,所有的坐标和尺寸必须在实际的位传送之前转换为设备坐标。cx和cy同时用于源和目标设备描述表,所以必须分别转换为设备描述表自己的单位。(?)
三、PatBlt函数:绘制填充图形
语法如下:PatBlt(hdc,x,y,cx,cy,dwROP);
GDI位图对象:也称为设备相关位图或者DDB
一、创建DDB
DDB是Windows图形设备接口的图形对象之一(其中还包括画笔、画刷、字体、元文件和调色板)。这些图形对象保存在GDI模式内部,由应用程序软件的数字句柄引用。用HBITMAP类型的变量存储DDB句柄:HBITMAP hBitmap。然后通过调用某个创建DDB的函数来获得句柄,这有三个函数。这些函数分配并初始化GDI内存中的一些内存来存储关于位图的信息,以及实际位图的位信息。应用程序不能直接访问这段内存。位图与设备描述表无关。程序使用完位图以后,就清除这段内存:DeleteObject(hBitmap);
三个函数:
CreateBitmap(cx,cy,cPlanes,cBitsPixel,bits);
CreateCompatibleBitmap(hdc,cx,cy);创建一个与设备兼容的位图。
CreateBitmapIndirect(&bitmap);bitmap是BITMAP类型的结构。
二、位图位
获得和设置像素位:
GetBitmapBits(hBitmap,cBytes,&bits);
SetBitmapBits(hBitmap,cBytes,&bits);
cBytes指明要复制的字节数,bits是其大小至少为cBytes的缓冲区。
像素实际上并不涉及任何固定的颜色,它只是一个值,与在视频板上的调色板查找表是的索引值有关。
基本的规则:不要用CreateBitmap、CreateBitmapIndirect或SetBitmapBits来设置彩色DDB的位,只能安全地设置单色DDB的位。
三、内存设备描述表
设备描述表指的是某个图形输出设备(如显示器或打印机),及其设备驱动程序。
内存设备描述表只位于内存中,它不是真正的图形输出设备,但可以说与某个实际设备“兼容”。在有某个实际设备的设备描述表之后,就可以创建内存设备描述表:
hdcMem=CreateCompatibleDC(hdc);
如果其参数为NULL,则创建一个与显示器相兼容的内存设备描述表。最后用DeleteDC来清除。
内存设备描述表有一个与实际光栅设备相同的、单色、1像素宽、1像素高的显示表面。然后,通过将一个GDI位图对象选进内存设备描述表来完成扩大显示表面的工作:SelectObject(hdcMem,hBitmap);
内存设备描述表是唯一一种可以选进位图的设备描述表。SelectObject调用以后,DDB就是内存设备描述表的显示表面。可以在其上进行各种处理实际设备描述表的每项操作。
批注:内存设备描述表实际上提供了一种在内存和显示器间进行位图传送的方法。
四、加载位图资源
也可用LoadBitmap函数来获得GDI位图对象句柄。
五、单色位图格式
对于单色位图,可以用一个字节数组来描述位图中的各个像素。
先用BITMAP结构来定义位图的信息,再用BYTE类型的数组来定义各个像素定义的各个位,最后用CreateBitmap、CreateBitmapIndirect来创建位图。
与设备无关的位图
DDB的位格式高度依赖于设备,所以它不适用于图像交换。DDB内没有色彩表来指定位图的位与色彩之间的联系,它仅当在Windows会话的生存期内被创建和清除时才有意义。
设备无关的位图(DIB)提供了适用于交换的图像文件格式。DIB内的位图几乎没有被压缩,适用于在程序中直接操作。如果在内存中有DIB,就可以提供指向该DIB的指针作为某些函数的参数,来显示DIB或把DIB转化为DDB。
DIB文件格式
DIB作为一种文件格式,它的扩展名为.BMP,在极少数的情况下为.DIB。Windows使用的位图图像被当作DIB文件创建,并作为只读资源存储在程序的可执行文件中。
程序能将DIB文件减去前14个字节加载到连续的内存块中形成“紧缩DIB(packed-DIB)格式的位图”。程序可以使用紧缩DIB格式,通过Windows剪帖板来交换图像或创建画刷,也或完全访问DIB的内容,并以任意方式修改DIB。
一、OS/2风格的DIB
DIB文件有4个主要的部分:
文件头、信息头、RGB色彩表(不一定有)和位图像素位。
前两部分保看成是一种C的数据结构,第三部分是数据结构的数组。
对内存中紧缩的DIB格式只有3个部分,就是缺少文件头。
DIB文件以14个字节的BITMAPFILEHEADER结构的文件头开始,指出了文件的类型、文件大小及像素位的偏移量;此后是12个字节的BITMAPCOREHEADER结构,指出了DIB的大小及每像素的位数。
BITMAPCOREHEADER结构中的bcBitCount字段一般为1、4、8或24,分别对应2色、16色、256色和全色的DIB。对于前三种BITMAPCOREHEADER后紧跟色彩表,对24位DIB,则无色彩表。
色彩表是一个RGBTRIPLE结构(此结构每个为3字节大小)的数组,数组中的每个元素代表图像中的每种颜色(指这种图像由多少种颜色来描述,如8位图就有256种颜色,这256种颜色就由这个数组的各个元素来指定,这个数组的大小就是256)。
二、从下向上
DIB中的像素位是按水平行组织的,常称“扫描线”。行数为BITMAPCOREHEADER结构中的bcHeight字段。但DIB从图像的底行开始,从下往上扫过图像。
所以,在DIB中,图像的底行是文件的第一行,图像的顶行是文件的最后一行。这个文件的第一行指的是DIB文件的色彩表后的位图像素位的第一个像素行,最后一行是位图像素位的最后一行。
三、DIB像素位
DIB文件的最后部分由实际的DIB的像素位组成。像素位是由从图像的底行开始,并沿着图像向上增长的水平行组织的。DIB中的行数为BITMAPCOREHEADER结构的bcHeight字段,每行的像素数为该结构的bcWidth字段。每行从最左边的像素数开始,直到图像的右边。每个像素的位数可以从bcBitCount字段获取,为1、4、8或24。
1位DIB:每字节为8像素,最左边的像素是第一个字节的最高位,色彩表中有2项。
4位DIB:每字节为2像素,最左边的像素是第一个字节的高4位,色彩表中有16项。
8位DIB:每字节为1像素,色彩表中有256项。
24位DIB:每3字节表示1个像素,每个字节代表红、绿和蓝的颜色值,就可以描述三种颜色的256个值,所以这个DIB无色彩表。
四、扩展的Windows DIB
这种DIB格式与前面的格式一样,以BITMAPFILEHEADER结构开始,但是接着是40字节的BITMAPINFOHEADER结构,而不是12字节的BITMAPCOREHEADER结构。另一个变化是:对于使用BITMAPINFOHEADER结构的1位、4位和8位DIB,色彩表不是RGBTRIPLE结构的数组,而是4字节的RGBQUAD结构的数组。
BITMAPINFOHEADER结构的biClrUsed是非常重要的字段,它影响色彩表中条目的数量,对于4位和8位的DIB,它能分别指出色彩表的条目数小于16或256个;对于16位、24位或32位DIB也可以为非0,在这种情况下Windows不使用色彩表来解释像素位,但它指出DIB中色彩表的大小,程序使用该信息来设置调色板在256显示器上显示DIB。
总结如下:
对于1位DIB,biClrUsed始终是0或2。色彩表始终有2个条目。
对于4位DIB,如果biClrUsed是0或16,则色彩表有16个条目,如果是从2到15的数,则指的是色彩表中的条目数。每个像素值的最大值比该数小1。
对于8位DIB,如果biClrUsed是0或256,则色彩表有256个条目,如果是从2到255的数,则指的是色彩表中的条目数。每个像素值的最大值比该数小1。
对于16位、24位或32位DIB,biClrUsed通常为0。如果不为0,则指的是色彩表中的条目数。运行于256色视频适配器的应用程序使用这些条目来为DIB设置调色板。
由上可知,对于1、4、8和24位的DIB,像素位的组织与OS/2兼容的DIB是相同的,但在后来的DIB中,24位DIB可以有色彩表了。
五、8位灰度DIB
8位DIB分为两类:灰度DIB和混色DIB。
若某些灰度DIB中有一个等于64的biClrUsed字段,则指出了色彩表中有64个条目,这些条目通常以上升的灰度级排列,也就是说色彩表中红绿蓝值相等时,这时若为8位,则其具有整个灰度组成的色彩表,这时,像素值自身就代表了灰色的程度(但不是说像素值与实际的RGB值相等),所以进行某些图像处理时,仅需处理像素值就可以了。
六、DIB压缩
BITMAPINFOHEADER结构的biCompression字段可以为4个常量之一,它们是:BI_RGB、BI_RLE8、BI_RLE4或BI_BIFIELDS,在WINGDI.H头文件中有定义,值分别为0,1,2,3。此字段有两个用途:对于4位和8位DIB,指出像素位用一种行程编码方式压缩了;对于16位和32位DIB,指出颜色掩蔽是否用于对像素位进行编码。
1、 行程编码(run-length: RLE压缩):对4位和8位
对于1位和24位DIB,biCompression字段始终是BI_RGB;对于4位和8位DIB,此字段是BI_RGB,像素位存储方式和前面的DIB一样,若是BI_RLE4或BI_RLE8,则使用行程编码。
行程编码是根据DIB映像在一行内经常有相同的像素串这个事实进行压缩的。
8位DIB的RLE:解码时,成对查看DIB数据字节。
(1) 如果第一个字节非0,则它就是行程的重复因子,随后的像素值被重复多次。如,对0x05 0x27,解码后为0x27 0x27 0x27 0x27 0x27
(2) 如果第二字节为n=0x03-0xFF中的一个,则说明要使用接下来的n个像素值。如,序列0x00 0x06 0x45 0x32 0x77 0x34 0x59 0x90,则解码后为0x45 0x32 0x77 0x34 0x59 0x90。由于总是两字节地检查,所以这此序列总是以2字节边界排列,所以若第二字节为奇数,则序列内就有一个未用的多余字节,如,0x00 0x05 0x45 0x32 0x77 0x34 0x59 0x00,则解码后为0x45 0x32 0x77 0x34 0x59
(3) 若2字节为00 02,则后面的2字节分别为dx和dy指出了在现有的(x,y)[这对数字在开始时为(0,0),在解码时,每对一个像素解码,x的值加1,每完成一行就将x置0,y加1]这个解码位置上移到(x+dx,y+dy)上。
(4) 若2字节为00 01和00 00,分别说明图像结束和行结束。
对于4位DIB的RLE编码方法相同,但字节和像素之间不是一对一的关系,如,序列0x07 0x35 0x05 0x24,则解码为0x35 0x35 0x35 0x32 0x42 0x42
2、 颜色掩蔽(color masking) :对16位和32位DIB
在biCompression字段为BI_RGB时:
16位DIB编码方式:红、绿、蓝三种颜色各使用5位,对于行内的第一个像素,蓝色值是第1字节的最低5位。绿色值在第一和第二字节中都有位:绿色值的两个最高位是第二个字节中的两个最低位,绿色值的3个最低位是第一个字节的3个最高位。红色值是第二个字节中的2到6位。第二个字节的最高位是0。之所以要这样安排,是因为在以16位字访问像素值时,应先取低字节,在取完二个字节后形成的像素值以红、绿、蓝排列。
32位DIB编码方式:由于每个字节是4字节,所以前三个字节为红、绿、蓝,最后字节为0。
在biCompression字段为BI_BITFIELDS时:紧跟DIB的BITMAPINFOHEADER结构的是三个32位颜色掩码,分别用于红、绿、蓝。然后用C的按位与操作符(&)把这些掩码应用于16位或32位的像素值上。
打开和显示
两个函数:SetDIBitsToDevice和StretchDIBits
它们都使用存储在内存中的DIB,并能显示整个DIB或它的矩形部分。当使用SetDIBitsToDevice时,以像素为单位显示的图像大小与DIB的像素大小相同。StretchDIBits则能缩放DIB尺寸的行和列,从而在输出设备上显示一个特定的大小。
1、两个函数的需要
DIB文件能被加载到内存中。如果除了文件头外,整个文件被存储在连续的内存块中,那么指向该块开始的指针被称作指向紧缩DIB的指针。
SetDIBitsToDevice和StretchDIBits函数需要的信息包括一个指向DIB的BITMAPINFO结构的指针,也即指向紧缩DIB的指针,还有就是一个指向像素位的指针。之所以要两个指针,是因为这两部分在内存的两个块中。除了这两个指针,还需要DIB的像素高度和宽度。
2、像素到像素:SetDIBitsToDevice函数
此函数显示DIB,并不对其进行拉伸或缩放。DIB的每个像素映射到输出设备的一个像素上。要调用SetDIBitsToDevice函数来显示整个DIB图像,需要下列信息:
hdc 目标表面的设备描述表句柄;
xDst和yDst 图像左上角的目标坐标;
cxDib和cyDib DIB的像素宽度和高度,cyDib是BITMAPINFOHEADER结构内biHeight字段的绝对值;
pInfo和pBits 指向位图信息部分和像素位的指针。
3、DIB的颠倒:从下向上的DIB的原点是位图图像的左下角,它是位图数据的第一行的第一个像素。从上向下的DIB的原点也是位图图像的左上角,但左下角是位图数据的最后一行的第一个像素。
4、按需放大:StretchDIBits函数