搜索
您的当前位置:首页bmp文件格式

bmp文件格式

来源:智榕旅游
BMP有单色,16色,256色,24位,32位几种类型位图。 BMP在磁盘上是按以下顺序存储的: ---------------header部分开始---------------- [位图文件头TBitmapFileHeader] // 14个字节 [位图信息头TBitmapInfo] // 40个字节 --------------- body部分开始 ---------------- [图像数据BGRA] // 32位位图含Alpha值 (1)位图文件头 ? 1 2 3 4 5 6 7 typedef struct tagBITMAPFILEHEADER { // bmfh WORD bfType; // 文件标识‘BM’ DWORD bfSize; // 用字节表示的整个文件的大小 WORD bfReserved1; // 保留字节,为0 WORD bfReserved2; // 保留字节,为0 DWORD bfOffBits; // 图象数据RGBA的起始地址的偏移值 为54 } BITMAPFILEHEADER; (2)位图信息头 ? 1 2 3 4 5 6 7 8 9 10 11 12 13 typedef struct tagBITMAPINFOHEADER{ // bmih DWORD biSize; // 位图信息头(Bitmap Info Header)的长度,为40 LONG biWidth; // 位图的宽度,以象素为单位 LONG biHeight; // 位图的高度,以象素为单位 WORD biPlanes; // 位图的位面数(注:该值将总是1) WORD biBitCount // 每个象素的位数,设为24(表示24Bit位图) DWORD biCompression; // 压缩说明,设为0(不压缩) DWORD biSizeImage; // 用字节数表示的位图数据的大小(该数必须是4的倍数) LONG biXPelsPerMeter; // 用象素/米表示的水平分辨率 (一般设为0) LONG biYPelsPerMeter; // 用象素/米表示的垂直分辨率(一般设为0) DWORD biClrUsed; // 位图使用的颜色数(一般设为0) DWORD biClrImportant; // 指定重要的颜色数(一般设为0) } BITMAPINFOHEADER; (3)图像数据BGRA 单色: 1个bit表示1个像素 16色: 4个bit表示1个像素 256色: 8个bit表示1个像素 24位: 24个bit表示1个像素 //BGR 32位: 32个bit表示1个像素 //BGRA *** 数据存放顺序 *** 注:图片的左下角为图像文件数据块的起点,从左到右,从下到上依次存储图像各像素值。如上图所示! (4)需要注意的几点 a. 像素中个颜色分量顺序为 BGRA -- Blue Green Red Alpha 以下是RGB宏的定义 ? 1 #define RGB(r, g ,b) ((DWORD) (((BYTE) (r) | \\ 2 ((WORD) (g) << 8)) | \\ 3 (((DWORD) (BYTE) (b)) << 16))) 可通过BYTE GetRValue(DWORD rgb) BYTE GetGValue(DWORD rgb) BYTE GetBValue(DWORD rgb)来获取各个分量的值。 b. 对于数据区域而言,每行的数据它必须凑满4字节,如果没有满,则用冗余的数据来补齐。 如:对于24位位图,假设一行有w个像素,那么一行的像素数为:4*((3*w+3)/4) // 注意(3*w+3)/4是在做整除操作

RGB24

RGB24使用24位来表示一个像素,RGB分量都用8位表示,取值范围为0-255。注意在内存中RGB各分量的排列顺序为:BGR BGRBGR…。通常可以使用RGBTRIPLE数据结构来操作一个像素,它的定义为: typedefstructtagRGBTRIPLE {

BYTE rgbtBlue; // 蓝色分量 BYTE rgbtGreen; // 绿色分量 BYTE rgbtRed; // 红色分量 } RGBTRIPLE; RGB32

RGB32使用32位来表示一个像素,RGB分量各用去8位,剩下的8位用作Alpha通道或者不用。(ARGB32就是带Alpha通道的RGB32。)注意在内存中RGB各分量的排列顺序为:BGRA BGRABGRA…。通常可以使用RGBQUAD数据结构来操作一个像素,它的定义为: typedefstructtagRGBQUAD { BYTE rgbBlue; // 蓝色分量 BYTE rgbGreen; // 绿色分量 BYTE rgbRed; // 红色分量

BYTE rgbReserved; // 保留字节(用作Alpha通道或忽略) } RGBQUAD。

就是多了一个透明通道,直接右移8为丢掉这个8位信息就可以了

HBITMAP ConvTo24Bit( HBITMAP h32 ) {

BITMAP bm;

GetObject( h32, sizeof( BITMAP ), &bm ); BITMAPINFOHEADER bi = { 0 }; void *pBits;

bi.biSize = sizeof( BITMAPINFOHEADER ); bi.biBitCount = 24; bi.biPlanes = 1;

bi.biWidth = bm.bmWidth; bi.biHeight = bm.bmHeight;

HDC hDC = GetDC( NULL );

HBITMAP h24 = CreateDIBSection( hDC, ( LPBITMAPINFO )&bi, DIB_RGB_COLORS, &pBits, NULL, 0 ); HDC hMemDC1 = CreateCompatibleDC( hDC ); HDC hMemDC2 = CreateCompatibleDC( hDC );

HBITMAP h32Old = ( HBITMAP )SelectObject( hMemDC1, h32 ); HBITMAP h24Old = ( HBITMAP )SelectObject( hMemDC2, h24 );

BitBlt( hMemDC2, 0, 0, bm.bmWidth, bm.bmHeight, hMemDC1, 0, 0, SRCCOPY ); SelectObject( hMemDC1, h32Old ); SelectObject( hMemDC2, h24Old ); DeleteDC( hMemDC1 ); DeleteDC( hMemDC2 ); ReleaseDC( NULL, hDC ); // now ok, 24bit bmp... return h24;

// 或者改return HBITMAP为BYTE *

DWORD dwImageSize = ( ( ( ( bm.bmWidth * 24 ) + 31 ) >> 5 ) << 2 ) * bm.bmHeight; LPBYTE lpData = ( LPBYTE )new BYTE[sizeof( BITMAPINFOHEADER ) + dwImageSize]; memcpy( ( LPBITMAPINFOHEADER )lpData, &bi, sizeof( BITMAPINFOHEADER ) ); memcpy( ( LPVOID )lpData + sizeof( BITMAPINFOHEADER ), pBits, dwImageSize ); DeleteObject( h24 ); return lpData; }

在早前的一篇文章中我曾经研究过带有 alpha 通道的图标,实际上 XP 系统已经开始支持这样的图标,也就是32 bpp(bits per pixel)的图标了。在本文最后给出的MSDN链接中可以介绍开发者如何创建 32 bpp的图标,不过不幸的是,VS开发环境不支持编辑这样的图标,而且原生的Photoshop也不支持(尽管有ICO格式插件),只能借助其他专业的图标制作工具,同样不幸的是,其他图标制作工具我用的并不顺手(至少没有PS那样熟练),所以我只能借助 Photoshop 和图标制作工具两者同时使用,从而可以完成制作 32 bpp图标。

(一)关于AlphaBlend;

AlphaBlend是提供 alpha 通道的贴图的 API 函数,即每个像素都带有一个独立的 alpha 值,去指定该像素在最终合成结果中占据的比例,其作用就相当于Photoshop中的图层蒙版。这是 Msimg32.dll 中提供的函数,因此我们要使用这个函数,用以下语句指定 lib 文件(或在项目属性中添加):

#pragma comment(lib, \"Msimg32.lib\")

这个函数是从一个 HDC 拷贝到另一个 HDC,选入DC的图片需要具有4个通道,即按照地址从低到高的顺序是B,G,R,A。在提供alhpa通道的图片格式里比较常用的是PNG格式,在 .net 里,GDI+可以从PNG图片加载出一个位图,然后用Graphics.DrawImage就可以看到合成的效果了,使用起来是非常简单的。在 C++ 中的贴图则分为数种(BitBlt,StretchBlt,PlgBlt,MaskBlt,TransparentBlt,AlphaBlend ...),前面大部分都是完整像素的传送和位操作,要实现两个像素的 alpha 合成,显然要使用的是AlphaBlend,由于这个函数在调用前还有一个特殊要求(如果你没有注意到这个要求,则很可能会对调用结果感到困惑),它在MSDN中的位置比较隐晦,是在介绍其最后一个参数 BLENDFUNCTION 中的一个成员(AlphaFormat)的取值时提到的:

这个要求是: 在调用AlhpaBlend之前,图片的 alpha 通道必须先应用到 RGB 通道上(premultiplied alpha)。

在之前我的文章中已给出了对一个CImage对象应用 alpha 通道的代码。这里我们不继续给出他的代码,现在假设我把一个PNG存储为32bpp的BMP 位图,然后可以从文件加载或者添加到PE文件的资源里面。然后就可以使用LoadImage从文件加载或者用LoadBitmap从资源加载了,如果从文件加载,必须指定一个标志:LR_CREATEDIBSECTION。

假设从资源中加载了这个位图,我们可以用下面的代码,预先应用alhpa通道:

inti, j; BITMAP bm;

BITMAPINFO bminfo;

memset(&bminfo, 0, sizeof(bminfo));

HBITMAP m_hBitmap = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_BITMAP3)); GetObject(m_hBitmap, sizeof(bm), &bm);

//在这里应用alpha通道 //bminfo

//扫描行宽度(实际上图片大小一致) int stride = bm.bmWidthBytes;

bminfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bminfo.bmiHeader.biPlanes = 1; bminfo.bmiHeader.biBitCount = 32; bminfo.bmiHeader.biWidth = bm.bmWidth; bminfo.bmiHeader.biHeight = bm.bmHeight;

bminfo.bmiHeader.biSizeImage = bm.bmHeight * stride;

BYTE* lpBits = (BYTE*)malloc(bminfo.bmiHeader.biSizeImage); memset(lpBits, 0xcc, bminfo.bmiHeader.biSizeImage); HDC hDC = GetDC(hWnd);

int lines = GetDIBits(hDC, m_hBitmap, 0, bminfo.bmiHeader.biHeight, lpBits, &bminfo, DIB_RGB_COLORS);

//应用alpha通道 BYTE* pPixel; floatalphaFactor;

for(j = 0; jfor(i = 0; ipPixel = lpBits + j*stride + i*4;

alphaFactor = (float)pPixel[3] / (float)0xff; pPixel[0] = (BYTE)(pPixel[0] * alphaFactor); pPixel[1] = (BYTE)(pPixel[1] * alphaFactor); pPixel[2] = (BYTE)(pPixel[2] * alphaFactor); } } //

SetDIBits(hDC, m_hBitmap, 0, bminfo.bmiHeader.biHeight, lpBits, &bminfo, DIB_RGB_COLORS); ReleaseDC(hWnd, hDC); free(lpBits);

//CImageimg;

//img.Attach(m_hBitmap);

//img.Save(\"应用alpha通道后.bmp\"); //img.Detach();

下图可直观的看到应用前和应用后的图片的区别,左侧是原始位图,其右侧是应用了Alpha通道后的位图,这就是在调用AlphaBlend之前应该选入Src DC的位图。虽然我们可以在运行时再去应用 alpha 通道,但是假如图片是已经确定的话(例如添加到PE的资源),那么我们为什么不先把比较消耗精力的这一步事先做掉呢?

上面的代码中,加载位图以后,为了修改像素,我使用GetDIBits函数去获取像素数据的一个拷贝,为此需要提供一个HDC,如果不提供HDC,就无法得到位图的像素(这让我有点不理解,我只想得到DIB的数据而已,不关心设备,但是API一定要求提供HDC有点不合情理),所以这里先获取程序的主窗口的HDC,得到位图数据然后我们把alhpa通道数据应用到 RGB 通道上。显然这样处理以后,图片会比原来的“变暗”一些,图像中完全透明的部分在结果中将完全变成黑色,半透明的部分会有所变暗。这样处理的要求可能是 API 觉得这样它的计算量可以有所减少(实际上也没少到哪里去),因为RGB通道可以直接和背景色被加权后(1- alpha/255) 的结果做加法。

这样处理后的位图就可以选入DC,然后调用AlhpaBlend函数了。这个函数的参数列表几乎和TransparentBlt一致,所以如果你的代码中使用了TransparentBlt则前面的参数无须改动,只需注意这个函数最后一个参数是 BLENDFUNCTION,从名称看仿佛是混合函数,实际上它只是一个含有4个整数的结构体而已。这个参数的设置决定了如何合成。SourceConstantAlpha提供了整体的 Alpha 值,相当于PS中的图层不透明度,它是作用于整体的。Alpha 通道则相当于PS中的蒙版,控制每个像素的参与合成比例,是一对一的作用在每个像素上的。关于成员的设置可以参考MSDN。下面就是使用AlhpaBlend的代码:

HDC hMemDC = CreateCompatibleDC(hdc);

HGDIOBJ hOldBitmap = SelectObject(hMemDC, m_hBitmap);

BLENDFUNCTION blendFunc;

blendFunc.BlendOp = AC_SRC_OVER; blendFunc.BlendFlags = 0;

blendFunc.SourceConstantAlpha = 255; //整体的不透明度 blendFunc.AlphaFormat = AC_SRC_ALPHA;

AlphaBlend(hdc, 150, 50, 256, 256, hMemDC, 0, 0, 256, 256, blendFunc);

SelectObject(hMemDC, hOldBitmap); DeleteDC(hMemDC);

效果如下图所示,这是把图像在纯色背景上合成的结果,可以看出图像的半透明部分(例如底部阴影)受到的背景色影响。如果背景是其他图片,则可以看到两个图片合成的结果。

(二)适用于XP系统中的反锯齿图标。

关于AlphaBlend就简单说到这里。AlphaBlend可以使图片完美的融入到任何的背景中,而不必关心背景的内容,这是其最大的优点。图标同样具有这个要求,图标的格式是在 Windows 的早期定义的,那时候的设备条件也远没有发展到现在的地步,所以那时定义的图标,只需要定义透明部分,不透明部分就够了,所以图标的 MASK 基于节省存储空间的考虑被定义成了单色位图(二元图)。然后到了XP时代,随着硬件水平改善,CPU的运算速度,显示器的表达颜色数量,存储设备的空间都大大提高,对 UI 美观性的要求又开始突出起来,仅提高图标图片本身的颜色数量(从16色,256色仅仅提高到24bpp)还不够,于是 XP 引入了带有 alpha 的 32 bpp图标,图标格式的定义没有改变,由于32 bpp图标具有 alpha 通道,所以其mask数据块已经变得可有可无了,因为其作用完全可以由 alpha 通道提供。实际上从更合理的角度考虑,图标中的每个图片都有一个图片信息头,在图标的图片信息头中,再提供一个 mask 的bpp数据(而不是像现在这样一律假定bpp为1),将会比现在的格式定义更完美。显然,使用带有 alpha 通道的图标绘制要比之前的简单图标工作量更大,原来只需要两次位操作性质的数据块传送,效率很高,而 Alpha 合成则无法整块传送,需逐点合成。

现在的 XP 系统中大量的应用了 32 BPP 的图标,这些图标通常带有渐隐的阴影效果,可以完美融合在背景中,因此也称作反锯齿图标。而早期的图标文件的像素要么透明,要么完全可见,因此是能够看出锯齿痕迹的。下面就是这两种图标的显示效果的区别:

上面的是 32 bpp的图标,可以看到它在任意背景上都能呈现视觉感舒适的阴影效果,而下面的是普通图标,没有办法提供完美的阴影效果。现在商业软件基本都提供了这种反锯齿的 32bpp 图标,比如qq等等。

windows为了适应多种硬件条件考虑,建议图标含有多个图片,首先按照颜色数量(BPP)从低到高排列,然后再同一个BPP中按照尺寸从大到小排列,系统在显示时从图标中按照一些原则(具体准则请参考MSDN)去抽取图标最适合的图片。对 32 BPP的图标,系统的建议是提供 3 种典型尺寸(48*48,32*32,16*16),尺寸24*24一般是在开始菜单上使用,属于可选的,三种典型BPP(16色,256色,真彩色(bpp = 24或32))。因此要在程序中使用反锯齿图标,我们通常至少需要 9 幅图片。

但是在IDE中我们没法编辑这样的图标,注意如果导入了32bpp的图标到IDE中也决不能用IDE去做任何修改!否则很可能损坏其显示效果。因此我们需要借助专用的图标制作工具。这里我使用的是:IconWorkshop,这是老外编写的(一般看起来比较好比较强大比较细心的东西的作者不可能是中国人,这是一个规律。。。),网络有免费的汉化版本下载。但是可能是我不熟悉的缘故,它的编辑功能我用的不惯,所以我先在 Photoshop 中编辑图片,然后存储为 PNG 格式,再用IconWorkshop打开PNG,然后就是复制粘贴,IconWorkshop有个很方便的功能,可以自动的以现有图片为基础产生所有其他大小和BPP的图片,然后再保存为 ICO 格式就可以了。

假如不自己制作,也可以借助工具(例如ICONPRO)从系统的 shell32.dll 中导出一些图标,把这样的图标添加到资源中,然后可以绘制他们,但是要注意,不能用常规的方法去做,否则绘制出来的图标是看不到反锯齿效果的。正确的方法是用LoadImage指定 LR_CREATEDIBSECTION 去加载图标,然后用DrawIconEx去指定大小的去绘制。代码如下:

inti, left = 10, top = 10;

inticonSizes[] = { 16, 24, 32, 48 };

for(i = 0; i<4; i++) {

Rectangle(hdc, left - 2, top - 2, left + iconSizes[i] + 2, top + 2 + iconSizes[i]); HICON hIcon = (HICON)LoadImage(hInst, MAKEINTRESOURCE(IDI_ICON3),

IMAGE_ICON, iconSizes[i], iconSizes[i], LR_CREATEDIBSECTION | LR_DEFAULTCOLOR); DrawIconEx(hdc, left, top, hIcon, iconSizes[i], iconSizes[i], 0, NULL, DI_NORMAL); //DrawIcon(hdc, left, 80, hIcon); left += iconSizes[i] + 8; DestroyIcon(hIcon); }

绘制的效果如下所示,这个图标就是我从 Shell32.dll 中抽取的图标,可以看到其柔和的阴影,我把图标绘制在比真正尺寸少许放大的白色矩形上,这样可以很容易看出图标的尺寸大概有多大,这些图片都是图标实际提供的(而不是从缩放得到),因此能够保证设计时的效果:

反锯齿的图标也可以放在 Static 图形控件中,显示效果是同样的,当然我们要先借助其他工具制作出反锯齿的图标。实际上不仅仅是图标,还有很多位置的小位图,比如自定义绘制菜单项左侧的小位图,各种TreeView,ListView等控件中使用的小位图,可能都是用 32 BPP 的图片通过 alpha 合成来实现的,这样就可达到更柔和更美观的显示效果。

(三)我所编写的范例程序;

(1)我写了一个小范例程序用来演示AlphaBlend的效果,同时也可读取一个32BPP的图片(BMP或者PNG格式),然后保存为应用了Alpha通道后的BMP格式位图。这样的结果图片就可以直接添加到项目资源中,然后应用到Alpha合成的场合。这个工具的主要代码在文中已经提供,也没有什么技术含量,因此不再提供源码下载。

(2)我写的另一个小程序用来打开一个ICO文件,然后可以展示图标中每个图像的XOR Mask,AND Mask, XOR中的Alpha通道(仅32bpp具有)部分。右下角是通过加载图标的方式绘制的图标。通过上方的组合框可以切换图片中的图像。这个工具还有一个功能,可以把图标的当前图像另存为 BMP 格式的位图文件(然后就可以在Photoshop中观察其 Alpha 通道)。通过这个工具,主要可以观察 32 bpp的图标的 Alpha 通道,以帮助理解反锯齿效果的原理,你可看到,AND MASK一定是“锯齿”的,因为它是二元图像只有两种颜色,如果有Alhpa通道,Alpha 通道会和AND MASK很像,但是黑白是相反的(因为它们的应用场合不同,因此黑白色的意义也不同,AND MASK 用于在背景上擦出黑色的绘制区域,其白色的意义是“保留背景原样”,Alpha 通道用于像素合成,其白色的意义是“此处像素完全不透明”),Alpha 通道的黑白色之间一般会有柔和的过渡(不然提供 Alpha 通道的意义就不大了),这正是图标绘制出来以后具有反锯齿效果的原因。其效果如下所示:

在这个工具中值得一提的是,对于bpp为16的图像在绘制 AND MASK 时必须特别处理,bpp的取值通常是1,4,8,16,24,32。BPP 为 1,4,8 (注意和bpp = 8的灰度图像是有区别的)的彩色索引图像需要图像提供调色板(我在绘制时,把彩色索引图像升级成了 24 bpp的像素数据块)。bpp等于16,24和32的图像都是通过像素数据块本身来提供 RGB 通道的。bpp=16的特殊在于,它用两个字节(WORD)表示一个像素。

通道如何分布取决于 biCompression 的取值:

如果 biCompression 是 BI_RGB,表示无压缩,没有调色板。对于biCompression为 BI_RGB 时,从低位到高位每5位表示一个通道(最高位没有用处,也没有 Alpha 通道),每个通道只有 2^5=32 级灰度,三个通道分享两个字节。这样在绘制 AND MASK 时不能使用在 24bpp 及以上的方式去设置像素数据(每个通道可独占一个字节)。例如在 AND MASK 中,白色是 0x7FFF(最高位为0),黑色是 0x0000,最大亮度是31 ( 0x1F )。

如果 biCompression 是 BI_BITFIELDS,(备注:这个值仅可能在bpp为16或32的图像中可能使用),则调色板中提供三个元素,分别是 RGB 通道的 mask ,用于指定某个通道在像素数据的占用的是哪些位。例如如果 RGB 通道各占用 5 位,那么它们的 mask 分别是 0x7C00,0x03E0,0x001F。

这个小工具的源码下载链接如下:

http://files.cnblogs.com/hoodlum1980/IconImg_src.rar

参考文献:

(1)本文说明了如何创建WINXP图标(给图标设计师阅读)

ms-help://MS.VSCC.v80/MS.MSDN.v80/MS.WIN32COM.v10.en/dnwxp/html/winxpicons.htm#winxpicons_step5

(2)本文比较重要的是,介绍了如何使用XP风格控件(接受系统样式的管理)。和32位的图标(支持alpha通道)

ms-help://MS.VSCC.v80/MS.MSDN.v80/MS.WIN32COM.v10.en/shellcc/platform/commctls/userex/cookbook.htm

(3)关于ICO文件格式,可以参考我之前写的博客:《[VC6] 图像文件格式数据查看器》

在这里我再简要总结下ICO文件格式:

Header: (6个字节。含图像个数)

Entries: (含每个图像的信息,这里的高度等于实际的高度,但这里的尺寸数据大小只有1个字节,因此应该以后面的BitmapInfoHeader中的尺寸为准) Images:

[0] : BitmapInfoHeader (这里的高度是实际的二倍,图像的大小应该以这里的数据为准)

RGBQUAD[] (调色板,它有没有,如果有则含有多少个颜色,都取决于BitmapInfoHeader中的信息,通常在bpp<=8 时具有)

XOR MASK Bits (扫描行按照4 bytes 对齐,bpp由前面的信息指定) AND MASK Bits (扫描行按照 4 bytes 对齐,bpp = 1)

因篇幅问题不能全部显示,请点此查看更多更全内容

Top