第1章 Windows位图和调色板 11位图和调色板的概念 如今 Windows(3x以及95,98,N系列已经成为绝大多数用户使用的操作系统,它比DOS 成功的一个重要因素是它可视化的漂亮界面。那么 Windows是如何显示图象的呢?这就要 谈到位图( bitmap) 我们知道,普通的显示器屏幕是由许许多多点构成的,我们称之为象素。显示时采用扫描的 方法:电子枪每次从左到右扫描一行,为每个象素着色,然后从上到下这样扫描若干行,就 扫过了一屏。为了防止闪烁,每秒要重复上述过程几十次。例如我们常说的屏幕分辨率为 640×480,刷新频率为70Hz,意思是说每行要扫描640个象素,一共有480行,每秒重复扫 描屏幕70次 我们称这种显示器为位映象设备。所谓位映象,就是指一个二维的象素矩阵,而位图就是采 用位映象方法显示和存储的图象。举个例子,图1.1是一幅普通的黑白位图,图1.2是被放 大后的图,图中每个方格代表了一个象素。我们可以看到:整个骷髅就是由这样一些黑点和 白点组成的。 图1.1骷髅 图12放大后的骷髅位图 那么,彩色图是怎么回事呢? 我们先来说说三元色RGB概念 我们知道,自然界中的所有颜色都可以由红、绿、蓝(R,G,B组合而成。有的颜色含有红 色成分多一些,如深红:有的含有红色成分少一些,如浅红。针对含有红色成分的多少,可 以分成0到255共256个等级,0级表示不含红色成分:255级表示含有100%的红色成分 同样,绿色和蓝色也被分成256级。这种分级概念称为量化。 这样,根据红、绿、蓝各种不同的组合我们就能表示出256×256×256,约1600万种颜色。 这么多颜色对于我们人眼来说已经足够丰富了 表11常见颜色的RGB组合值 颜色 R
第 1 章 Windows 位图和调色板 1.1 位图和调色板的概念 如今 Windows(3.x 以及 95,98,NT)系列已经成为绝大多数用户使用的操作系统,它比 DOS 成功的一个重要因素是它可视化的漂亮界面。那么 Windows 是如何显示图象的呢?这就要 谈到位图(bitmap)。 我们知道,普通的显示器屏幕是由许许多多点构成的,我们称之为象素。显示时采用扫描的 方法:电子枪每次从左到右扫描一行,为每个象素着色,然后从上到下这样扫描若干行,就 扫过了一屏。为了防止闪烁,每秒要重复上述过程几十次。例如我们常说的屏幕分辨率为 640×480,刷新频率为 70Hz,意思是说每行要扫描 640 个象素,一共有 480 行,每秒重复扫 描屏幕 70 次。 我们称这种显示器为位映象设备。所谓位映象,就是指一个二维的象素矩阵,而位图就是采 用位映象方法显示和存储的图象。举个例子,图 1.1 是一幅普通的黑白位图,图 1.2 是被放 大后的图,图中每个方格代表了一个象素。我们可以看到:整个骷髅就是由这样一些黑点和 白点组成的。 图 1.1 骷髅 图 1.2 放大后的骷髅位图 那么,彩色图是怎么回事呢? 我们先来说说三元色 RGB 概念。 我们知道,自然界中的所有颜色都可以由红、绿、蓝(R,G,B)组合而成。有的颜色含有红 色成分多一些,如深红;有的含有红色成分少一些,如浅红。针对含有红色成分的多少,可 以分成 0 到 255 共 256 个等级,0 级表示不含红色成分;255 级表示含有 100%的红色成分。 同样,绿色和蓝色也被分成 256 级。这种分级概念称为量化。 这样,根据红、绿、蓝各种不同的组合我们就能表示出 256×256×256,约 1600 万种颜色。 这么多颜色对于我们人眼来说已经足够丰富了。 表 1.1 常见颜色的 RGB 组合值 颜色 R G B 红 255 0 0
0 255 蓝绿黄紫青白黑灰 0 255 255 255 255 255 255 55 128 128 你大概已经明白了,当一幅图中每个象素赋予不同的RGB值时,能呈现出五彩缤纷的颜色 了,这样就形成了彩色图。的确是这样的,但实际上的做法还有些差别 让我们来看看下面的例子。 有一个长宽各为200个象素,颜色数为16色的彩色图,每一个象素都用R、G、B三个分 量表示。因为每个分量有256个级别,要用8位(bit),即一个字节(byte)来表示,所以每个 象素需要用3个字节。整个图象要用200×200×3,约120k字节,可不是一个小数目呀!如 果我们用下面的方法,就能省的多 因为是一个16色图,也就是说这幅图中最多只有16种颜色,我们可以用一个表:表中的每 行记录一种颜色的R、G、B值。这样当我们表示一个象素的颜色时,只需要指出该颜色 是在第几行,即该颜色在表中的索引值。举个例子,如果表的第0行为255,0,0(红色), 那么当某个象素为红色时,只需要标明0即可 让我们再来计算一下:16种状态可以用4位(bt)表示,所以一个象素要用半个字节。整个图 象要用200×200×0.5,约20字节,再加上表占用的字节为3×16=48字节整个占用的字节数 约为前面的1/6,省很多吧 这张R、G、B的表,就是我们常说的调色板( Palette),另一种叫法是颜色查找表LUT(Look Up Table),似乎更确切一些。 Windows位图中便用到了调色板技术。其实不光是 Windows 位图,许多图象文件格式如pcx、tf、gif等都用到了。所以很好地掌握调色板的概念是十分 有用的。 有一种图,它的颜色数高达256×256×256种,也就是说包含我们上述提到的R、G、B颜色 表示方法中所有的颜色,这种图叫做真彩色图( true color)。真彩色图并不是说一幅图包含了 所有的颜色,而是说它具有显示所有颜色的能力,即最多可以包含所有的颜色。表示真彩色 图时,每个象素直接用R、G、B三个分量字节表示,而不采用调色板技术。原因很明显 如果用调色板,表示一个象素也要用24位,这是因为每种颜色的索引要用24位(因为总共 有24种颜色,即调色板有2x行),和直接用R,G,B三个分量表示用的字节数一样,不 但没有任何便宜,还要加上一个256×256×256×3个字节的大调色板。所以真彩色图直接用R G、B三个分量表示,它又叫做24位色图 12bmp文件格式
蓝 0 255 0 绿 0 0 255 黄 255 255 0 紫 255 0 255 青 0 255 255 白 255 255 255 黑 0 0 0 灰 128 128 128 你大概已经明白了,当一幅图中每个象素赋予不同的 RGB 值时,能呈现出五彩缤纷的颜色 了,这样就形成了彩色图。的确是这样的,但实际上的做法还有些差别。 让我们来看看下面的例子。 有一个长宽各为 200 个象素,颜色数为 16 色的彩色图,每一个象素都用 R、G、B 三个分 量表示。因为每个分量有 256 个级别,要用 8 位(bit),即一个字节(byte)来表示,所以每个 象素需要用 3 个字节。整个图象要用 200×200×3,约 120k 字节,可不是一个小数目呀!如 果我们用下面的方法,就能省的多。 因为是一个 16 色图,也就是说这幅图中最多只有 16 种颜色,我们可以用一个表:表中的每 一行记录一种颜色的 R、G、B 值。这样当我们表示一个象素的颜色时,只需要指出该颜色 是在第几行,即该颜色在表中的索引值。举个例子,如果表的第 0 行为 255,0,0(红色), 那么当某个象素为红色时,只需要标明 0 即可。 让我们再来计算一下:16 种状态可以用 4 位(bit)表示,所以一个象素要用半个字节。整个图 象要用 200×200×0.5,约 20k 字节,再加上表占用的字节为 3×16=48 字节.整个占用的字节数 约为前面的 1/6,省很多吧? 这张 R、G、B 的表,就是我们常说的调色板(Palette),另一种叫法是颜色查找表 LUT(Look Up Table),似乎更确切一些。Windows 位图中便用到了调色板技术。其实不光是 Windows 位图,许多图象文件格式如 pcx、tif、gif 等都用到了。所以很好地掌握调色板的概念是十分 有用的。 有一种图,它的颜色数高达 256×256×256 种,也就是说包含我们上述提到的 R、G、B 颜色 表示方法中所有的颜色,这种图叫做真彩色图(true color)。真彩色图并不是说一幅图包含了 所有的颜色,而是说它具有显示所有颜色的能力,即最多可以包含所有的颜色。表示真彩色 图时,每个象素直接用 R、G、B 三个分量字节表示,而不采用调色板技术。原因很明显: 如果用调色板,表示一个象素也要用 24 位,这是因为每种颜色的索引要用 24 位(因为总共 有 2 24 种颜色,即调色板有 2 24 行),和直接用 R,G,B 三个分量表示用的字节数一样,不 但没有任何便宜,还要加上一个 256×256×256×3 个字节的大调色板。所以真彩色图直接用 R、 G、B 三个分量表示,它又叫做 24 位色图。 1.2 bmp 文件格式
介绍完位图和调色板的概念,下面就让我们来看一看 Windows的位图文件(bmp文件)的格 式是什么样子的 bmp文件大体上分成四个部分,如图1.3所示 位图文件头 BITMAPFILEHEADER 位图信息头 BITMAPINFOHEADER 调色板 Palette 实际的位图数据 Image Date 图1.3 Windows位图文件结构示意图 第一部分为位图文件头 BITMAPFILEHEADER,是一个结构,其定义如下: typedef struct tag BITMAPFILEHEADER i WORD DWORD bfSize WORD bfreservedl WORD bfReserved2 DWORD bfoffBits A BITMAPFILEHEADER; 这个结构的长度是固定的,为14个字节(WORD为无符号16位整数, DWORD为无符号32 位整数,各个域的说明如下 rtYpe 指定文件类型,必须是0x424D,即字符串“BM”,也就是说所有bmp文件的头两个字节 都是“BM” baIze 指定文件大小,包括这14个字节 bfReservedl, bfReserved2 为保留字,不用考虑 为从文件头到实际的位图数据的偏移字节数,即图1.3中前三个部分的长度之和 第二部分为位图信息头 BITMAPINFOHEADER,也是一个结构,其定义如下 typedef struct tag BITMAPINFOHEADER! dword biSize LONG bitwidth LONG biWeight; WORD biPlar WORD biBitcount DWORD coMpression dword biSizelmage LONG biXPelsPerMeter. LONG biY PelsPerMeter
介绍完位图和调色板的概念,下面就让我们来看一看 Windows 的位图文件(.bmp 文件)的格 式是什么样子的。 bmp 文件大体上分成四个部分,如图 1.3 所示。 位图文件头 BITMAPFILEHEADER 位图信息头 BITMAPINFOHEADER 调色板 Palette 实际的位图数据 ImageDate 图 1.3 Windows 位图文件结构示意图 第一部分为位图文件头 BITMAPFILEHEADER,是一个结构,其定义如下: typedef struct tagBITMAPFILEHEADER { WORD bfType; DWORD bfSize; WORD bfReserved1; WORD bfReserved2; DWORD bfOffBits; } BITMAPFILEHEADER; 这个结构的长度是固定的,为 14 个字节(WORD 为无符号 16 位整数,DWORD 为无符号 32 位整数),各个域的说明如下: bfType 指定文件类型,必须是 0x424D,即字符串“BM”,也就是说所有.bmp 文件的头两个字节 都是“BM”。 bfSize 指定文件大小,包括这 14 个字节。 bfReserved1,bfReserved2 为保留字,不用考虑 bfOffBits 为从文件头到实际的位图数据的偏移字节数,即图 1.3 中前三个部分的长度之和。 第二部分为位图信息头 BITMAPINFOHEADER,也是一个结构,其定义如下: typedef struct tagBITMAPINFOHEADER{ DWORD biSize; LONG biWidth; LONG biHeight; WORD biPlanes; WORD biBitCount DWORD biCompression; DWORD biSizeImage; LONG biXPelsPerMeter; LONG biYPelsPerMeter;
dword biClrUsed DWoRd biClrlmportant 1 BITMAPINFOHEADER 这个结构的长度是固定的,为40个字节(LONG为32位整数),各个域的说明如下 biSize 指定这个结构的长度,为40。 bitwidth 指定图象的宽度,单位是象素。 指定图象的高度,单位是象素。 biPl 必须是1,不用考虑。 biBit Count 指定表示颜色时要用到的位数,常用的值为1(黑白二色图),4(16色图),8(256色,24真彩色 图新的bmp格式支持32位色,这里就不做讨论了) biCompressio 指定位图是否压缩,有效的值为 BI RGB, BI RLE8,BRLE4, BI BITFIELDS(都是一些 Windows定义好的常量)。要说明的是, Windows位图可以采用RLE4,和RLE8的压缩格式 但用的不多。我们今后所讨论的只有第一种不压缩的情况,即 coMpression为 BI RGB的 情况 biSizelmage 指定实际的位图数据占用的字节数,其实也可以从以下的公式中计算出来 biSizelmage=bi width'x biHeight 要注意的是:上述公式中的 bi width'必须是4的整倍数(所以不是 bitwidth,而是 bi width 表示大于或等于 bi width的,最接近4的整倍数。举个例子,如果 bi width=240,则 bi width’=240;如果 biTwidth=241, bi width’=244)。 如果 coMpression为 BI RGB,则该项可能为零 biX PelsPer Meter 指定目标设备的水平分辨率,单位是每米的象素个数,关于分辨率的概念,我们将在第4 章详细介绍 biY PelsPer Meter 指定目标设备的垂直分辨率,单位同上。 biCirUsed 指定本图象实际用到的颜色数,如果该值为零,则用到的颜色数为2 oi Bitton biCIrlmportant 指定本图象中重要的颜色数,如果该值为零,则认为所有的颜色都是重要的
DWORD biClrUsed; DWORD biClrImportant; } BITMAPINFOHEADER; 这个结构的长度是固定的,为 40 个字节(LONG 为 32 位整数),各个域的说明如下: biSize 指定这个结构的长度,为 40。 biWidth 指定图象的宽度,单位是象素。 biHeight 指定图象的高度,单位是象素。 biPlanes 必须是 1,不用考虑。 biBitCount 指定表示颜色时要用到的位数,常用的值为 1(黑白二色图), 4(16 色图), 8(256 色), 24(真彩色 图)(新的.bmp 格式支持 32 位色,这里就不做讨论了)。 biCompression 指定位图是否压缩,有效的值为 BI_RGB,BI_RLE8,BI_RLE4,BI_BITFIELDS(都是一些 Windows 定义好的常量)。要说明的是,Windows 位图可以采用 RLE4,和 RLE8 的压缩格式, 但用的不多。我们今后所讨论的只有第一种不压缩的情况,即 biCompression 为 BI_RGB 的 情况。 biSizeImage 指定实际的位图数据占用的字节数,其实也可以从以下的公式中计算出来: biSizeImage=biWidth’ × biHeight 要注意的是:上述公式中的 biWidth’必须是 4 的整倍数(所以不是 biWidth,而是 biWidth’, 表示大于或等于 biWidth 的,最接近 4 的整倍数。举个例子,如果 biWidth=240,则 biWidth’=240;如果 biWidth=241,biWidth’=244)。 如果 biCompression 为 BI_RGB,则该项可能为零 biXPelsPerMeter 指定目标设备的水平分辨率,单位是每米的象素个数,关于分辨率的概念,我们将在第 4 章详细介绍。 biYPelsPerMeter 指定目标设备的垂直分辨率,单位同上。 biClrUsed 指定本图象实际用到的颜色数,如果该值为零,则用到的颜色数为 2 biBitCount。 biClrImportant 指定本图象中重要的颜色数,如果该值为零,则认为所有的颜色都是重要的
第三部分为调色板 Palette,当然,这里是对那些需要调色板的位图文件而言的。有些位图, 如真彩色图,前面已经讲过,是不需要调色板的, BITMAPINFOHEADER后直接是位图数 据 调色板实际上是一个数组,共有 biClrUsed个元素(如果该值为零,则有2boun个元素) 数组中每个元素的类型是一个 RGBQUAD结构,占4个字节,其定义如下: typedef struct tagRGBQUAD i BYTE rgbBlue;∥该颜色的蓝色分量 BYTE rgb Greer,∥该颜色的绿色分量 BYTE rgbRed;∥该颜色的红色分量 BYTE rgbReserved,∥保留值 9 RGBQUAD, 第四部分就是实际的图象数据了。对于用到调色板的位图,图象数据就是该象素颜在调色板 中的索引值。对于真彩色图,图象数据就是实际的R、G、B值。下面针对2色、16色、256 色位图和真彩色位图分别介绍。 对于2色位图,用1位就可以表示该象素的颜色(一般0表示黑,1表示白),所以一个字节 可以表示8个象素 对于16色位图,用4位可以表示一个象素的颜色,所以一个字节可以表示2个象素。 对于256色位图,一个字节刚好可以表示1个象素。 对于真彩色图,三个字节才能表示1个象素,哇,好费空间呀!没办法,谁叫你想让图的颜 色显得更亮丽呢,有得必有失嘛 要注意两点 (1)每一行的字节数必须是4的整倍数,如果不是,则需要补齐。这在前面介绍 bisizelmage 时已经提到了。 (2)一般来说,bMP文件的数据从下到上,从左到右的。也就是说,从文件中最先读到的 是图象最下面一行的左边第一个象素,然后是左边第二个象素.接下来是倒数第二行左边 第一个象素,左边第二个象素.依次类推,最后得到的是最上面一行的最右一个象素。 好了,终于介绍完bmp文件结构了,是不是觉得头有些大?别着急,对照着下面的程序, 你就会很清楚了(我最爱看源程序了,呵呵)。 13显示一个bmp文件的C程序 下面的函数 Load BmpFile,其功能是从一个bmp文件中读取数据(包括 BITMAPINFOHEADER,调色板和实际图象数据),将其存储在一个全局内存句柄 hImgData 中,这个 hImgData将在以后的图象处理程序中用到。同时填写一个类型为 HBITMAP的全 局变量 hBitmap和一个类型为 HPALETTE的全局变量 pAlette。这两个变量将在处理 WM PAINT消息时用到,用来显示位图。该函数的两个参数分别是用来显示位图的窗口句 柄,和bmp文件名(全路径)。当函数成功时,返回TRUE,否则返回 FALSE。 bItmapfileheader bf
第三部分为调色板 Palette,当然,这里是对那些需要调色板的位图文件而言的。有些位图, 如真彩色图,前面已经讲过,是不需要调色板的,BITMAPINFOHEADER 后直接是位图数 据。 调色板实际上是一个数组,共有 biClrUsed 个元素(如果该值为零,则有 2 biBitCount 个元素)。 数组中每个元素的类型是一个 RGBQUAD 结构,占 4 个字节,其定义如下: typedef struct tagRGBQUAD { BYTE rgbBlue; //该颜色的蓝色分量 BYTE rgbGreen; //该颜色的绿色分量 BYTE rgbRed; //该颜色的红色分量 BYTE rgbReserved; //保留值 } RGBQUAD; 第四部分就是实际的图象数据了。对于用到调色板的位图,图象数据就是该象素颜在调色板 中的索引值。对于真彩色图,图象数据就是实际的 R、G、B 值。下面针对 2 色、16 色、256 色位图和真彩色位图分别介绍。 对于 2 色位图,用 1 位就可以表示该象素的颜色(一般 0 表示黑,1 表示白),所以一个字节 可以表示 8 个象素。 对于 16 色位图,用 4 位可以表示一个象素的颜色,所以一个字节可以表示 2 个象素。 对于 256 色位图,一个字节刚好可以表示 1 个象素。 对于真彩色图,三个字节才能表示 1 个象素,哇,好费空间呀!没办法,谁叫你想让图的颜 色显得更亮丽呢,有得必有失嘛。 要注意两点: (1) 每一行的字节数必须是 4 的整倍数,如果不是,则需要补齐。这在前面介绍 biSizeImage 时已经提到了。 (2) 一般来说,.bMP 文件的数据从下到上,从左到右的。也就是说,从文件中最先读到的 是图象最下面一行的左边第一个象素,然后是左边第二个象素……接下来是倒数第二行左边 第一个象素,左边第二个象素……依次类推 ,最后得到的是最上面一行的最右一个象素。 好了,终于介绍完 bmp 文件结构了,是不是觉得头有些大?别着急,对照着下面的程序, 你就会很清楚了(我最爱看源程序了,呵呵)。 1.3 显示一个 bmp 文件的 C 程序 下面的函数 LoadBmpFile ,其功能是从一个 .bmp 文件中读取数据 ( 包 括 BITMAPINFOHEADER,调色板和实际图象数据),将其存储在一个全局内存句柄 hImgData 中,这个 hImgData 将在以后的图象处理程序中用到。同时填写一个类型为 HBITMAP 的全 局变量 hBitmap 和一个类型为 HPALETTE 的全局变量 hPalette。这两个变量将在处理 WM_PAINT 消息时用到,用来显示位图。该函数的两个参数分别是用来显示位图的窗口句 柄,和.bmp 文件名(全路径)。当函数成功时,返回 TRUE,否则返回 FALSE。 BITMAPFILEHEADER bf;
BITMAPINFOHEADER bi BOOL Load Bmp File(HWNd hWnd, char*BmpFileName) HFILE hf,∥文件句柄 ∥指向 BITMAPINFOHEADER结构的指针 LPBITMAPINFOHEADER Iplmg Data LOGPALETTE *pPal;∥指向逻辑调色板结构的指针 LPRGBQUAD IpRGB,∥指向 RGBQUAD结构的指针 HPALETTE hPrey palette;∥来保存设备中原来的调色板 HDC hDc,∥设备句柄 HLOCAL hPa,∥存储调色板的局部内存句柄 DWORD LineBytes,∥每一行的字节数 DWORD Imgsize,∥实际的图象数据占用的字节数 ∥实际用到的颜色数,即调色板数组中的颜色个数 DWORD Num Colors if(hf- lopen(BmpFileName, OF READ))=HFILE ERROR) Message Box(hWnd, " File c: Itest. bmp not found! " "Error Message!" MB OKJMB ICONEXCLAMATION) return False;/打开文件错误,返回 ∥将 BITMAPFILEHEADER结构从文件中读出,填写到bf中 Iread(hf, (LPSTR)&bf, sizeof( BI TMAPFILEHEADER)) ∥将 BITMAPINFOHEADER结构从文件中读出,填写到bi中 Iread(hf, (LPSTR)&bi, sizeof(BITMAPINFOHEADER)) ∥我们定义了一个宏# define Widthy tes(i)(+31)/32*4)上面曾经 ∥提到过,每一行的字节数必须是4的整倍数,只要调用 ZIDTHBYTES( bi biTwidth*bi. biBit Count就能完成这一换算。举一个例 ∥子,对于2色图,如果图象宽是31,则每一行需要31位存储,合3个 ∥字节加7位,因为字节数必须是4的整倍数,所以应该是4,而此时的 / bi width=31 bi BitCount=1, WIDTHBYTES(31*1)=4,和我们设想的一样 ∥举一个256色的例子,如果图象宽是31,则每一行需要31个字节存 ∥储,因为字节数必须是4的整倍数,所以应该是32,而此时的 / bitwidth=31, bi Bitcount=8, WIDTHBYTES(31*8)=32,我们设想的一样。你 ∥以多举几个例子来验证一下 LineBytes为每一行的字节数 Line Bytes=(DWORD)WIDTHBYTES(bibi Width* bi. biBitCount); ImgSize为实际的图象数据占用的字节数
BITMAPINFOHEADER bi; BOOL LoadBmpFile (HWND hWnd,char *BmpFileName) { HFILE hf; //文件句柄 //指向 BITMAPINFOHEADER 结构的指针 LPBITMAPINFOHEADER lpImgData; LOGPALETTE *pPal; //指向逻辑调色板结构的指针 LPRGBQUAD lpRGB; //指向 RGBQUAD 结构的指针 HPALETTE hPrevPalette; //用来保存设备中原来的调色板 HDC hDc; //设备句柄 HLOCAL hPal; //存储调色板的局部内存句柄 DWORD LineBytes; //每一行的字节数 DWORD ImgSize; //实际的图象数据占用的字节数 //实际用到的颜色数 ,即调色板数组中的颜色个数 DWORD NumColors; DWORD i; if((hf=_lopen(BmpFileName,OF_READ))==HFILE_ERROR){ MessageBox(hWnd,"File c:\\test.bmp not found!","Error Message", MB_OK|MB_ICONEXCLAMATION); return FALSE; //打开文件错误,返回 } //将 BITMAPFILEHEADER 结构从文件中读出,填写到 bf 中 _lread(hf,(LPSTR)&bf,sizeof(BITMAPFILEHEADER)); //将 BITMAPINFOHEADER 结构从文件中读出,填写到 bi 中 _lread(hf,(LPSTR)&bi,sizeof(BITMAPINFOHEADER)); //我们定义了一个宏 #define WIDTHBYTES(i) ((i+31)/32*4)上面曾经 //提到过,每一行的字节数必须是 4 的整倍数,只要调用 //WIDTHBYTES(bi.biWidth*bi.biBitCount)就能完成这一换算。举一个例 //子,对于 2 色图,如果图象宽是 31,则每一行需要 31 位存储,合 3 个 //字节加 7 位,因为字节数必须是 4 的整倍数,所以应该是 4,而此时的 //biWidth=31,biBitCount=1,WIDTHBYTES(31*1)=4,和我们设想的一样。 //再举一个 256 色的例子,如果图象宽是 31,则每一行需要 31 个字节存 //储,因为字节数必须是 4 的整倍数,所以应该是 32,而此时的 //biWidth=31,biBitCount=8,WIDTHBYTES(31*8)=32,我们设想的一样。你可 //以多举几个例子来验证一下 //LineBytes 为每一行的字节数 LineBytes=(DWORD)WIDTHBYTES(bi.biWidth*bi.biBitCount); //ImgSize 为实际的图象数据占用的字节数
ImgSize=(DWORD)LineBytes"bi. biHeight / Num Colors为实际用到的颜色数,即调色板数组中的颜色个数 if(bi. biCIrUsed!=0) ∥如果 bi biClrUsed不为零,即为实际用到的颜色数 NumColors-=(DWORD)bi. biClrUsed else∥否则,用到的颜色数为2 biBitCount switch(bi. biBitCount)i Num Colors=2. Num Colors=16 break. Num Colors=256 Num Colors=0;∥|对于真彩色图,没用到调色板 default:∥不处理其它的颜色数,认为出错。 Message Box(hWnd, Invalid color numbers! " "Error Message MB OK MB ICONEXCLAMATION Iclose(hf); return FALSE;关闭文件,返回 FALSE if(bf. bfoffBits! =(DWORD)(Num Colors*sizeof( RGBQUAD)+ sizeof( BITMAPFILEHEADER)+ sizeof(BITMAPINFOHEADER))) ∥计算出的偏移量与实际偏移量不符,一定是颜色数出错 Message Box(hWnd, "Invalid color numbers! " " Error Message MB OK MB ICONEXCLAMATION) Iclose(hf); return FAlse;∥关闭文件,返回 FALSE bf. bfSize=sizeof( BITMAPFILEHEADER)+sizeof( BI TMAPINFOHEADER)+ Num Colors*sizeof( RGBQUAD)+ImgSize, ∥分配内存,大小为 BITMAPINFOHEADER结构长度加调色板+实际位图
ImgSize=(DWORD)LineBytes*bi.biHeight; //NumColors 为实际用到的颜色数 ,即调色板数组中的颜色个数 if(bi.biClrUsed!=0) //如果 bi.biClrUsed 不为零,即为实际用到的颜色数 NumColors=(DWORD)bi.biClrUsed; else //否则,用到的颜色数为 2 biBitCount。 switch(bi.biBitCount){ case 1: NumColors=2; break; case 4: NumColors=16; break; case 8: NumColors=256; break; case 24: NumColors=0; //对于真彩色图,没用到调色板 break; default: //不处理其它的颜色数,认为出错。 MessageBox(hWnd,"Invalid color numbers!","Error Message", MB_OK|MB_ICONEXCLAMATION); _lclose(hf); return FALSE; //关闭文件,返回 FALSE } if(bf.bfOffBits!=(DWORD)(NumColors*sizeof(RGBQUAD)+ sizeof(BITMAPFILEHEADER)+ sizeof(BITMAPINFOHEADER))) { //计算出的偏移量与实际偏移量不符,一定是颜色数出错 MessageBox(hWnd,"Invalid color numbers!","Error Message", MB_OK|MB_ICONEXCLAMATION); _lclose(hf); return FALSE; //关闭文件,返回 FALSE } bf.bfSize=sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER)+ NumColors*sizeof(RGBQUAD)+ImgSize; //分配内存,大小为 BITMAPINFOHEADER 结构长度加调色板+实际位图
if((hmg Data=Global Alloc(GHND, (DWORD) (sizeof(BI TMAPINFOHEADER)+ Num Colors*sizeof(RGBQUAD) D==NULL ∥分配内存错误 Message Box(hWnd, Error alloc memory! " "Error Message", MB MB ICONEXCLAMATION) return FAlSE;∥)关闭文件,返回 FALSE ∥指针 Iplmg Data指向该内存区 lplmg Data=(LPBITMAPINFOHEADER)GlobalLock(hlmg Data ∥文件指针重新定位到 BITMAPINFOHEADER开始处 llseek(hf, sizeof( BITMAPFILEHEADER) SEEK SET ∥将文件内容读入 lplmg Data hread(hf, (char *)Iplmg Data, long)sizeof( BITMAPINFOHEADER) +(long)Num Colors*sizeof(RGBQUAD)ImgSize) Iclose(hf);∥关闭文件 if( Num Colors!=0)/ Num Colors不为零,说明用到了调色板 ∥.为逻辑调色板分配局部内存,大小为逻辑调色板结构长度加 //Num Colors PALETTENTRY hPal=LocalAlloc(LHND, sizeof(LOGPALETTE)+ Num Colors* sizeof(PALETTEENTRY)) ∥指针pPal指向该内存区 pPal =(LOGPALETTE *)LocalLock(hPal) ∥填写逻辑调色板结构的头 pPal->palNum Entries= Num Color pPal->pal Version=0x300 pRGB指向的是调色板开始的位置 IpRGB=(LPRGBQUAD)((LPSTR)Iplmg Data (DWORD)sizeof( BITMAPINFOHEADER)) ∥填写每一项 for (i=0; ipalPal[i]- pe Green=lpRGB->rgbGreen;
if((hImgData=GlobalAlloc(GHND,(DWORD) (sizeof(BITMAPINFOHEADER)+ NumColors*sizeof(RGBQUAD)+ ImgSize)))==NULL) { //分配内存错误 MessageBox(hWnd,"Error alloc memory!","ErrorMessage",MB_OK| MB_ICONEXCLAMATION); _lclose(hf); return FALSE; //关闭文件,返回 FALSE } //指针 lpImgData 指向该内存区 lpImgData=(LPBITMAPINFOHEADER)GlobalLock(hImgData); //文件指针重新定位到 BITMAPINFOHEADER 开始处 _llseek(hf,sizeof(BITMAPFILEHEADER),SEEK_SET); //将文件内容读入 lpImgData _hread(hf,(char *)lpImgData,(long)sizeof(BITMAPINFOHEADER) +(long)NumColors*sizeof(RGBQUAD)+ImgSize); _lclose(hf); //关闭文件 if(NumColors!=0) //NumColors 不为零,说明用到了调色板 { //为逻辑调色板分配局部内存,大小为逻辑调色板结构长度加 //NumColors 个 PALETTENTRY hPal=LocalAlloc(LHND,sizeof(LOGPALETTE)+ NumColors* sizeof(PALETTEENTRY)); //指针 pPal 指向该内存区 pPal =(LOGPALETTE *)LocalLock(hPal); //填写逻辑调色板结构的头 pPal->palNumEntries = NumColors; pPal->palVersion = 0x300; //lpRGB 指向的是调色板开始的位置 lpRGB = (LPRGBQUAD)((LPSTR)lpImgData + (DWORD)sizeof(BITMAPINFOHEADER)); //填写每一项 for (i = 0; i palPalEntry[i].peRed=lpRGB->rgbRed; pPal->palPalEntry[i].peGreen=lpRGB->rgbGreen;
pPal->palPalEntryli] peBlue=lpRGB->rgbBlue pPal->palPalEntryli]. peFlagS-=(BYTE)O IpRGB++;∥指针移到下一项 ∥产生逻辑调色板, pAlette是一个全局变量 hPalette=CreatePalette(pPal ∥释放局部内存 LocalUnlock(hPal) Local Free(hPal) ∥获得设备上下文句柄 hDc=GetDC(hWnd) f(pAlette)∥如果刚才产生了逻辑调色板 ∥将新的逻辑调色板选入DC,将旧的逻辑调色板句柄保存在/ hPrey Palette hPrevPalette=SelectPalette(hDc, hPalette, FALSE) RealizePalette(hDc ∥产生位图句柄 hBitmap=CreateDI Bitmap(hDc, (LPBITMAPINFOHEADER)Iplmg Data (LONG)CBM INIT. (LPSTR)Iplmg Data+sizeof( BITMAPINFOHEADER)+ Num Colors *sizeof(RGBQUAD), ( LPBITMAPINFO)Iplmg Data, DIB RGB COLORS) ∥将原来的调色板(如果有的话)选入设备上下文句柄 if(pAlette&& hPrevPalette) SelectPalette(hDc, hPrev Palette, FALSE) RealizePalette(hDc) ReleaseD(hWnd,hDe),∥释放设备上下文 GlobalUnlock( hIng Data);∥解锁内存区 return TRUE;∥成功返回 对上面的程序要说明两点 (1)对于需要调色板的图,要想正确地显示,必须根据bmp文件,产生逻辑调色板。产生 的方法是:①为逻辑调色板指针分配内存,大小为逻辑调色板结构( LOGPALETTE)长度加 Num Colors个 PALETTENTRY大小(调色板的每一项都是一个 PALETTEENTRY结构):② 填写逻辑调色板结构的头pal-> palNumEntries= Num Colors,pPal-> advErsion=0x300;③从
pPal->palPalEntry[i].peBlue=lpRGB->rgbBlue; pPal->palPalEntry[i].peFlags=(BYTE)0; lpRGB++; //指针移到下一项 } //产生逻辑调色板,hPalette 是一个全局变量 hPalette=CreatePalette(pPal); //释放局部内存 LocalUnlock(hPal); LocalFree(hPal); } //获得设备上下文句柄 hDc=GetDC(hWnd); if(hPalette) //如果刚才产生了逻辑调色板 { //将新的逻辑调色板选入 DC,将旧的逻辑调色板句柄保存在//hPrevPalette hPrevPalette=SelectPalette(hDc,hPalette,FALSE); RealizePalette(hDc); } //产生位图句柄 hBitmap=CreateDIBitmap(hDc,(LPBITMAPINFOHEADER)lpImgData, (LONG)CBM_INIT, (LPSTR)lpImgData+sizeof(BITMAPINFOHEADER)+NumColors*sizeof(RGBQUAD), (LPBITMAPINFO)lpImgData, DIB_RGB_COLORS); //将原来的调色板(如果有的话)选入设备上下文句柄 if(hPalette && hPrevPalette) { SelectPalette(hDc,hPrevPalette,FALSE); RealizePalette(hDc); } ReleaseDC(hWnd,hDc); //释放设备上下文 GlobalUnlock(hImgData); //解锁内存区 return TRUE; //成功返回 } 对上面的程序要说明两点: (1) 对于需要调色板的图,要想正确地显示,必须根据 bmp 文件,产生逻辑调色板。产生 的方法是:①为逻辑调色板指针分配内存,大小为逻辑调色板结构(LOGPALETTE)长度加 NumColors 个 PALETTENTRY 大小(调色板的每一项都是一个 PALETTEENTRY 结构);② 填写逻辑调色板结构的头 pPal->palNumEntries = NumColors; pPal->palVersion = 0x300;③从
文件中读取调色板的RGB值,填写到每一项中;④产生逻辑调色板 (2)产生位图 BITMAP)句柄,该项工作由函数 CreateDIBitmap来完成。 hBitmap=CreateDIBitmap(hDc, (LPBITMAPINFOHEADER)Iplmg Data, CBM INIT (LPSTR)Iplmg Data+sizeof(BI TMAPINFOHEADER)+Num Colors*sizeof( RGBQUAD) ( LPBITMAPINFO)Iplmg Data, DIB RGB COLORS); CreateD Bitmap的作用是产生一个和 Windows设备无关的位图。该函数的第一项参数为设 备上下文句柄。如果位图用到了调色板,要在调用 CreateDIBitmap之前将逻辑调色板选入 该设备上下文中,产生 hBitmap后,再把原调色板选入该设备上下文中,并释放该上下文; 第二项为指向 BITMAPINFOHEADER的指针:第三项就用常量 CBM INI,不用考虑;第四 项为指向调色板的指针;第五项为指向 BITMAPINFO(包括 BITMAPINFOHEADER调色板, 及实际的图象数据)的指针;第六项就用常量DⅠ B RGB COLORS,不用考虑。 上面提到了设备上下文,相信编过 Windows程序的读者对它并不陌生,这里再简单介绍 下。 Windows操作系统统一管理着诸如显示,打印等操作,将它们看作是一个个的设备, 每一个设备都有一个复杂的数据结构来维护。所谓设备上下文就是指这个数据结构。然而, 我们不能直接和这些设备上下文打交道,只能通过引用标识它的句柄(实际上是一个整数) 让 Windows去做相应的处理。 产生的逻辑调色板句柄 pAlette和位图句柄 hBitmap要在处理 WM PAINT消息时使用,这 样才能在屏幕上显示出来,处理过程如下面的程序。 hDC. hMemDC PAINTSTRUCT case WM PAINt hDC= Begin Paint(hwnd,&ps),∥获得屏幕设备上下文 f( hBitmap)/ hBitmap一开始是NUL,当不为NULL时表示有图 hMemDC= Create CompatibleDC(hDC;∥建立一个内存设备上下文 if( pAlette∥有调色板 ∥将调色板选入屏幕设备上下文 SelectPalette(hDC, hPalette, FALSE) ∥将调色板选入内存设备上下文 SelectPalette(hMemDC, hpalette, FALSE); RealizePalette(hDC) ∥将位图选入内存设备上下文 Selectobject(hMemDC, hBitmap)
文 件 中 读 取 调 色 板 的 RGB 值 , 填 写 到 每 一 项 中 ; ④ 产 生 逻 辑 调 色 板 : hPalette=CreatePalette(pPal)。 (2) 产生位图(BITMAP)句柄,该项工作由函数 CreateDIBitmap 来完成。 hBitmap=CreateDIBitmap(hDc,(LPBITMAPINFOHEADER)lpImgData, (LONG)CBM_INIT, (LPSTR)lpImgData+sizeof(BITMAPINFOHEADER)+NumColors*sizeof(RGBQUAD), (LPBITMAPINFO)lpImgData, DIB_RGB_COLORS); CreateDIBitmap 的作用是产生一个和 Windows 设备无关的位图。该函数的第一项参数为设 备上下文句柄。如果位图用到了调色板,要在调用 CreateDIBitmap 之前将逻辑调色板选入 该设备上下文中,产生 hBitmap 后,再把原调色板选入该设备上下文中,并释放该上下文; 第二项为指向 BITMAPINFOHEADER 的指针;第三项就用常量 CBM_INI,不用考虑;第四 项为指向调色板的指针;第五项为指向 BITMAPINFO(包括 BITMAPINFOHEADER,调色板, 及实际的图象数据)的指针;第六项就用常量 DIB_RGB_COLORS,不用考虑。 上面提到了设备上下文,相信编过 Windows 程序的读者对它并不陌生,这里再简单介绍一 下。Windows 操作系统统一管理着诸如显示,打印等操作,将它们看作是一个个的设备, 每一个设备都有一个复杂的数据结构来维护。所谓设备上下文就是指这个数据结构。然而, 我们不能直接和这些设备上下文打交道,只能通过引用标识它的句柄(实际上是一个整数), 让 Windows 去做相应的处理。 产生的逻辑调色板句柄 hPalette 和位图句柄 hBitmap 要在处理 WM_PAINT 消息时使用,这 样才能在屏幕上显示出来,处理过程如下面的程序。 Static HDC hDC,hMemDC; PAINTSTRUCT ps; case WM_PAINT: { hDC = BeginPaint(hwnd, &ps); //获得屏幕设备上下文 if (hBitmap) //hBitmap 一开始是 NULL,当不为 NULL 时表示有图 { hMemDC = CreateCompatibleDC(hDC); //建立一个内存设备上下文 if (hPalette) //有调色板 { //将调色板选入屏幕设备上下文 SelectPalette (hDC, hPalette, FALSE); //将调色板选入内存设备上下文 SelectPalette (hMemDC, hpalette, FALSE); RealizePalette (hDC); } //将位图选入内存设备上下文 SelectObject(hMemDC, hBitmap);