WIN32API讲座7

第七课∶位图

一、概述

      在Windows中每屏是一个图形图像,灵巧的Windows制作系统,面对庞大的图形编程任务,建立了为绘画多彩的边界、按钮、图标、字体的函数库。当然啦,通过Windows API,这些函数都是可调用的。所谓Windows显示屏幕以及数量众多的打印机其实都是属于“光栅设备”。在光栅设备中,一幅图象由多条扫描线以及能访问的单独像素构成。Windows也支持非光栅设备,比如绘图仪等,本人对此一无经验,无从谈起,想来也差不到哪儿去。以下只以显示器为重点展开讨论。

      计算机视频系统的核心是内存。该内存包含代表着显示图案的数据,而这些图案显示在监视器(显示器)上。每次鼠标移动时,内存中的少量数据发生变化。然后你会看到鼠标指针在屏幕上移动。每次以及每一个图形操作都会影响视频内存,因为GDI执行计算并以相应方式更改视频内存。计算机中还有一个存放图象数据的内存,叫做位图内存。位图内存与视频内存的重要的一个区别是∶位图内存看不到,而视频内存则能够看到。也就是说放在位图内存的图象数据并不反映在屏幕上,而视频内存中存放的图象信息则反映在监视器上。如果把驻留在位图内存的数据移动到视频内存中,那么图象将显示在监视器上。以一个桌面图标为例,图标从磁盘加载到内存中(位图内存)中,然后内存被移到视频内存中的适当地址上,这样当视频内存通过视频硬件被显示到屏幕上时,图标成为可见的。

      从图象的种类来讲,Windows中存在两种位图,一种叫与设备有关位图(或叫设备相关位图),另一种叫与设备无关位(或叫设备无关位图,或DIB)。除非特别声明,W indows中的位图都是与设备有关的位图。有些朋友一说位图,就可能想到BMP图象,但它是属于与设备无关的位图。在编程的领域我们常常说位图,一般并不是指BMP图象,而是指对他们来说也是想象中存在的那个叫与设备有关的位图。另一个区别与设备有关位图和与设备无关位图的重要依据是,判断该位图是否具有句柄。具有句柄的位图便是与设备有关的位图,因为它是GDI对象之一。常见的BMP图象则属于与设备无关的位图。我们一般不叫它位图,而是叫做“BMP图象”或者叫做“DIB”,它是一种数据的组织方式,并非GDI绘图对象。
      可以把与设备无关位图理解为对与设备有关位图数据的一种标准格式的数据保存方式。比如我们平常看到的附加名为BMP的文件。这种位图文件会在文件头上放置文件的组织信息,形式上讲和一些数据库文件的文件头起着同样的作用━━描述文件的结构。文件头后面紧跟着的是图象的颜色数据。由于这种机制的存在,使得与设备无关的位图可以在各种设备之间进行读写。

      与设备无关的位图只有一种格式,而与设备有关的位图则可能...大概有多少种设备就有多少种格式吧?鬼才知道!幸运的是您根本没有必要了解它的格式,只需简单理解为保存图象数据的内存块就可以了。就好像您跟本不用了解我长得什么模样,也照样能和我打交道一样。

       PC的视频系统显示一个像素的矩阵(整齐排列的小光点)。在一个基于字符的环境中,视频系统包含一套"硬件编程"的形状,代表标准字符的位置及各种画图字符(直线、角线、实线等等)和几个符号(笑脸、钻石、铲等)。通过发送给系统一个ASCII码或ANSI字符码来显示一个字符。而在Windows这样一个图形显示系统中,计算机及其软件定义出现在屏幕上的图形形状。这些形状是用位图来代表的。用这种方法的最主要的优点是图象和文本能以不同的大小、字体、方式来显示。那么,准确地说,什么是位图呢?它们是数据元素的集合,这些数据元素决定在每个屏幕位置上显示什么样的颜色。在单色的(黑和白)图形图像中,每位代表一个屏幕像素,0对应黑(没有颜色),1对应白(显示色)。不久您就会看到,位图能描述彩色图像。每个像素的位数决定能在单个位图中出现的不同色彩的数目。除了每个像素一位的单色视频,还有其他三种∶每个像素4位产生16种颜色,8位产生256种颜色,24位产生16'777'216种颜色(补充:我已经亲眼看到了32位显卡)。如下表所示∶

                         Windows支持的彩色

每个像素的位数       颜色总数                        典型设备
1                             2                            单色图形
4                            16                           标准备VGA
8                            256                         256色VGA
16                          32'768或65'536      32K或64K色SVGA
24                          16'777'216             24位真彩色设
       计算机显示系统的性能决定Windows对彩色图象的处理。现在常看到的显示器是SVGA(AGP也出来了,我的就是,但资料少无从谈起)。理论上讲,SVGA系统能显示惊人的含有16'777'216种颜色数组。把能够显示16'000'000种颜色范围的系统通常称为真彩色(true color)。那么"真彩色"的真实意思是什么呢?它源于对是否一个彩色显示器能显示无限大范围的颜色的想象,图象将看起来完全逼“真”。真彩色的显示用24位来决定每个像素的颜色。24位被分成三组,每组8位,来表示红、绿、蓝三种颜色。(参考∶自然界的颜色是通过红、绿、蓝(RGB)三种颜色来组合而成的。叫做三元色。)

二、与设备无关位图(DIB)

      所谓DIB是指与设备无关的位图。DIB并不是Windows的对象,它没有自己的句柄,实际上是一种数据文件。和很多数据文件相同,DIB位图文件的开头部分有它内在的数据结构描述部分。不少书做了一些示意图来说明这个问题,但我觉得还是亲眼目睹DIB文件的内部更好一些,百闻不如一睹嘛。
      现在我们准备要看的BMP文件是一个256色位图,文件名叫Hua.bmp ,这个文件曾在《VB前线》的演示程序第25号中使用过。

      首先让我们查看一下文件在磁盘中的大体状况∶
D:/vbplay/vbplay25/>dir

Volume in drive D is PRO       
 Volume Serial Number is 268F-AB4B
 Directory of D:/vbplay/vbplay25

.              <DIR>        03-27-99  20:47 .
..             <DIR>        03-27-99  20:47 ..
PLAY25       VBW           111  09-20-99  23:43 play25.vbw
MSSCCPRJ  SCC            193  04-07-99  23:52 MSSCCPRJ.SCC
BITMAP       CLS            12,711  06-16-99  21:27 Bitmap.cls
MYMEMORY CLS            3,419  04-08-99   2:07 MyMemory.cls            PLAY25       VBP            636  06-16-99  21:42 play25.vbp
HUA            BMP           96,446  04-04-99  12:06 hua.bmp
PL25_1       FRM           3,865  06-16-99  21:25 pl25_1.frm
         7 file(s)        117,381 bytes
         2 dir(s)     569,049,088 bytes free


      看到了吧?它的大小是96?46个字节。接下来,我们可以用DEBUG的d命令以16进制形式来显示文件的文件头信息。操作如下∶

D:/vbplay/vbplay25/>debug hua.bmp
-d 100 1000
0EAE:0100  42 4D BE 78 01 00 00 00-00 00 36 04 00 00 28 00   BM.x......6...(.
0EAE:0110  00 00 69 01 00 00 06 01-00 00 01 00 08 00 00 00   ..i.............
0EAE:0120  00 00 88 74 01 00 C4 0E-00 00 C4 0E 00 00 00 00   ...t............
0EAE:0130  00 00 00 00 00 00 00 00-00 00 80 80 80 00 00 00   ................
0EAE:0140  80 00 00 80 80 00 00 80-00 00 80 80 00 00 80 00   ................

0EAE:0150  00 00 80 00 80 00 40 80-80 00 40 40 00 00 FF 80   ......@...@@....
0EAE:0160  00 00 80 40 00 00 FF 00-40 00 00 40 80 00 FF FF   ...@....@..@....
省略了57行∶50 - 16 -1 = 39(16进制) = 57(10进制)
0EAE:0500  CC 00 AD E8 FD 00 78 AE-D3 00 1F 38 65 00 66 89   ......x....8e.f.
0EAE:0510  F0 00 63 9B C3 00 79 AD-DA 00 08 13 73 00 0A 1C   ..c...y.....s...
0EAE:0520  A5 00 BB ED FD 00 83 C1-E5 00 2A 50 74 00 7C B3   ..........*Pt.|.

0EAE:0530  ED 00 56 82 BD 00 B3 66-B3 B3 6C B3 BD 8B C9 C9   ..V....f..l.....
0EAE:0540  BD 50 21 60 6C 6C 44 44-B3 44 6C 86 44 66 44 D9   .P!`llDD.Dl.DfD.
0EAE:0550  44 B3 6C C9 87 7B B9 7B-44 BD 87 60 87 C9 6C AB   D.l..{.{D..`..l.
0EAE:0560  60 D6 6C AB AB 2A 88 55-88 7B 7B 7B 7B 3D 3D 3D   `.l..*.U.{{{{===
0EAE:0570  C5 3D C5 24 7B 7B 7B 7B-B4 B9 7B B9 B5 C6 3D 7B   .=.${{{{..{...={
(以下全部省略)
0EAE:1000  B3                                                .

-q
      注∶若使用D:/vbplay/vbplay25/>debug hua.bmp >> Temp.txt则可以把结果保存到Temp.txt文件,并可加以分析研究。但这样给出命令后什么都看不见。这时,你应当键入d 100 1000这个命令后回车,然后再按q就可以返回到DOS命令行提示符下。这段内容是这样获取的。详细内容,可参考有关DOS命令参考手册。用pctools5.0也可以。


      通过上述方式获取的是该位图文件第一个字节开始的若干个字节的16进制形式的文本内容。以下,我们具体分析一下。在分析以前为了在整体上把握DIB文件的结构,有必要先给出其整体情况,如下表格所示∶

DIB文件的组成

三大部分                                          字节                           说明
BITMAPFILEHEADER                        14                
用红色表示的部分
BITMAPINFO BITMAPINFOHEADER   40                 用深绿色表示的部分
RGBQUAD(结构数组)                         最小0,最大4×256。(在16位以上的位图中,根本不存在此部分)                                            用深红色表示的部分
数据部分                                           大于0(具体大小信息记录在BITMAPINFOHEADER结构中)                                 用下划线表示的部分
      可以看出,DIB的文件头主要由两个部分组成,即BITMAPFILEHEADER和BITMAPINFO,
       而BITMAPINFO又分为BITMAPINFOHEADER和RGBQUAD两个小部分。让我们一个一个分析。首先是开头以红色表示的14个字节,如下

42 4D BE 78 01 00 00 00-00 00 36 04 00 00
这是BITMAPFILEHEADER结构部分。BITMAPFILEHEADER结构如表所示∶


                    BITMAPFILEHEADER结构

结构内各字段                    数据类型                             说明
bfType                          Integer                    指定文件类型,必须 BM
bfSize                           Long                        指定位图文件大小,以字节为单位
bfReserved1                 Integer                    保留未用,必须设为0
bfReserved2                 Integer                    同上
bfOffBits                        Long                       从此结构到位图数据位的字节偏移量
      此结构,主要记录了DIB文件大小以及结构有关的信息。在很多情况下,可以计算来获取这些这些信息,所以很多人并不访问这个结构内的数据。不少人说
BITMAPFILEHEADER结构记录着无大用处的信息。其中bfOffBits说明的是从此结构到位图数据位的字节偏移量。通过对此字段的访问,我们可以知道这个位图文件的颜色数据是从哪里开始的,或者可以知道DIB文件头的结构有多长。


m_BMFileHeader.bfType = 42 4D

        这是一个Integer整型数据,对DIB文件来说此数据必须是42 4D。那么为什么定为42 4D呢,其实是在ASCII码表中42即十进制的66表示大写B,4D即十进制的77表示M,也就是说字符擝M數囊馐丁?梢远哉飧稣褪莸姆梦世磁卸衔募欠袷荄IB(或BMP)文件。

m_BMFileHeader.bfSize = BE 78 01 00 = &H178BE = 96446个字节

    说明文件的总长度。注意,右侧的数据是高字节,即右侧字节的地址比左侧字节的地址值大,所以BE 78 01 00 = &H178BE。在上面,用dir命令观察文件时,我们已经看到文件的长度正是96446个字节。如∶
    HUA      BMP       96,446  04-04-99  12:06 hua.bmp


m_BMFileHeader.bfReserved1 = 00 00  (系统保留,没有设置数据)
m_BMFileHeader.bfReserved2 = 00 00  (系统保留,没有设置数据)

m_BMFileHeader.bfOffBits = 36 04 00 00 = &H00000436 = 1078 =14 + 40 + 256 * 4

    (从文件开始处起,带下划线的数据位的以字节为单位的长度,总共1078个字节。对真彩色图象来说,这个数据经常是36 00 00 00,即54。说明文件头只有BITMAPFILEHEADER(14个字节) + BITMAPINFOHEADER(40个字节)的部分,即没有RGBQUAD部分。为什么没有RGBQUAD部分也可以?稍后你就明白怎么一回事。继续往下看,继续)

    与此结构紧连在一起的下一个叫BITMAPINFO的结构,其组成如下表所示∶


                  BITMAPINFO结构(表二)

结构内各字段                   数据类型                              说明
bmiHeader              BITMAPINFOHEADER      一个 BITMAPINFOHEADER
bmiColors                RGBQUAD                      一个 RGBQUAD结构组成的数组
      从此表中可以看出,结构内部还存在两个结构,即BITMAPINFOHEADER和RGBQUAD。因此,从某种意义上讲,位图文件的文件头结构可以说为三个结构来构成的: BITMAPFILEHEADER 、BITMAPINFOHEADER和RGBQUAD。但,必须认清的是BITMAPINFO结构并不是离开BITMAPINFOHEADER和RGBQUAD这两个结构而独立存在的。BITMAPINFOHEADER结构的长度是固定着的,为40个字节。BITMAPINFOHEADER结构的有关说明,可参考如下表∶


             BITMAPINFOHEADER结构(表三)

结构内各字段                      数据类型                   说明
biSize                              Long                   结构长度(40)
biWidth                           Long                   指定位图的宽度,以像素为单位
biHeight                          Long                   指定位图的高度,以像素为单位
biPlanes                         Integer                指定目标设备的级数(必须为 1 )
biBitCount                      Integer                每一个像素的位(1,4,8,16,24,32)
biCompression               Long                    指定压缩类型(BI_RGB 为不压缩)
biSizeImage                   Long                    指定图象的大小,以字节为单位
biXPelsPerMeter             Long                   指定设备水平分辨率,以每米的像素为单位
biYPelsPerMeter             Long                   垂直分辨率,其他同上
biClrUsed                       Long                   在颜色表中实际使用的色彩索引的个数,用O表示全要使用
biClrImportant               Long                   指定认为重要的颜色索引个数,用 0 表示所有颜色均重要
     BITMAPINFOHEADER结构主要记载了数据区的大小及颜色信息。必须认清的是,
biSizeImage字段说明的是图象的大小,也就是数据部分的大小,而不是文件的大小。如果我们只把位图的数据部分读入到内存(而把结果部分读入到数组),就需要按此大小来申请内存大小。为了便于对照,我把上面给出的示范数据在这里重贴一下了∶
0EAE:0100                                                28 00   BM.x......6...(.
0EAE:0110  00 00 69 01 00 00 06 01-00 00 01 00 08 00 00 00   ..i.............

0EAE:0120  00 00 88 74 01 00 C4 0E-00 00 C4 0E 00 00 00 00   ...t............
0EAE:0130  00 00 00 00 00 00                                    ................

    分析说明如下∶

biSize = 28 00 00 00 = &H28 = 40
此结构的长度,总共40个字节。总是这个数据。

 biWidth = 69 01 00 00 = &H169 = 361
 图象的宽度(像素质为单位)。把此图片放到PictureBox控件,并运行如下命令∶
 Text1.Text = Picture1.ScaleX(Picture1.Picture.Width, vbHimetric, vbPixels)
 结果为∶360.9827。可以看出VB的计算存在一点误差。但如果你用整形变量来读取这个数据,那么就一样了。

biHeigh = 06 01 00 00 = &H106 = 262
图象的高度,请参照bitWidth .

biPlanes = 01 00 = &H1 = 1   
必须为1。

biBitCount  = 08 00 = &H8 = 8
每个像素的位。当前我例举的这个叫hua.bmp的位图文件是以8个位来表示一个像素的,即一个字节。一个256(2的8次方)色位图能显示的颜色总数限定在256种颜色,但这并不意味着任何256色位图都只能显示相同的256种颜色。一个256色位图所能够显示的256种颜色被规定在该位图的RGBQUAD结构中。256色位图所能显示的颜色范围,能够达到设备所允许的范围(16位增强模式中可能的颜色数为2的16次方,24位真彩模式中可能的颜色数为2
的24次方),但必须是其中的256个颜色。RGBQUAD结构所描述的颜色值用来产生调色板。在这种情况下数据区内存放的是对这256种颜色的索引值,而不是实际颜色。当用1位来表示一个像素的时候,RGBQUAD的数组个数为2,存放着黑色和白色两个颜色,而数据区存放着1和0两个索引值;当用4位来表示一个像素的时候,则RGBQUAD的数组个数为16,存放着16种颜色,数据区存放着0至15的索引号;当8位的时候存放256种颜色,数据区存放着0至255的索引号;而当像素位超过或等于16位的时候,RGBQUAD结构部分不存在,数据区存放着以16位或24位表示的实际颜色。在这种时候就不存在调色板了。其中的原因一想就知道,难道我们要设上万种的调色板项?

biCopression = 00 00 00 00 = &H0 = 0
指定压缩类型。不是说BI_RGB 为不压缩吗?让我查查,BI_RGB究竟多少?
来喽! Const BI_RGB& = 0& ,呵呵,原来这种文件叫做没有压缩。
还有∶Const BI_RLE4& = 2&   和 Const BI_RLE8& = 1&   估计没有什么用处,想压缩就采用JPG吧,何必用BMP ?

biSizeImage = 88 74 01 00 = &H17488 = 95368
图象数据的长度(大小)。从数据关系上来讲,应该是∶图象数据的长度+文件头长度=文件长度。让我们验证一下,是否是这样∶

biSizeImage + m_BMFileHeader.bfOffBits
= 95368 + 1078 = 96446 = m_BMFileHeader.bfSize
@@~果然如此!

   

biXPelsPerMeter = C4 0E 00 00 = &HEC4 = 3780

biYPelsPerMeter = C4 0E 00 00 = &HEC4 = 3780

水平分辨率和垂直分辨率。

ibClrUsed = 00 00 00 00 = &H0 = 0

颜色表(调色板)中使用的索引个数。既然是0,说明全要使用了。全就是256个,再多了不可能。你千万不要用此数据来判断RGBQUAD数组的个数。

biClrImportant = 00 00 00 00 = &H0 = 0

指定认为重要的颜色索引个数,用 0 表示所有颜色均重要。请大家多多去选用0吧,这样才能有利于团结。

    与BITMAPINFOHEADER 结构不同RGBQUAD是一个数组,其长度(RGBQUAD结构长度×数组元素个数)可以是多种情况,但不能超过256个。RGBQUAD结构如表所示∶

RGBQUAD结构(表四)

结构内各字段 数据类型 说明
rgbBlue Byte 指定彩色中蓝色成分的多少
rgbGreen Byte 指定彩色中绿色成分的多少
rgbRed Byte 指定彩色中红色成分的多少
rgbReserved Byte 保留,可设为0
0EAE:0130                      00 00-00 00 80 80 80 00 00 00   ................
0EAE:0140  80 00 00 80 80 00 00 80-00 00 80 80 00 00 80 00   ................
0EAE:0150  00 00 80 00 80 00 40 80-80 00 40 40 00 00 FF 80   ......@...@@....
0EAE:0160  00 00 80 40 00 00 FF 00-40 00 00 40 80 00 FF FF   ...@....@..@....
以下省略(还很长)

您大概已经注意到了,我为什么用红色标示了一些 00 呢?原因是因为它们都是白痴,只占地方,不起作用,是微软公司保留的RGBQUAD结构中的rgbReserved字段。每个 00
和 00 和之间有三个字节,当然,他们分别表示RGB颜色的了。这样颜色表中索引号为0的颜色应当是等于RGB(&H0,&H0,&H0),索引号为1的颜色应当是等于RGB(&H80,&H80,
&H80),索引号为2的颜色那就是等于RGB(&H0,&H0,&H80),依次类推,至到索引号为255的那个颜色。

OKay!现在已经把文件头讲得够细的了,不能再细,再细了就没戏了。接着看一下数据部分。


0EAE:0530  ED 00 56 82 BD 00 B3 66-B3 B3 6C B3 BD 8B C9 C9   ..V....f..l.....
0EAE:0540  BD 50 21 60 6C 6C 44 44-B3 44 6C 86 44 66 44 D9   .P!`llDD.Dl.DfD.

以下省略

我们可以看到第一个像素点的数据是B3,即179,说明此颜色相当与颜色表中的索引号为237的那个颜色。那么那个颜色究竟什么颜色呢?糟糕,给我省略了,没有列出来。来找一个没有省略的吧,看看有没有。好象没有。算了,就到这里吧。其实找也是那么一回事。不过我还是告诉你吧,因为你自己找的话可能找不到。原因是∶行是以逆序存放的。
也就是说,位图文件的像素数据部分中的第一行实际上是图象中的最后一行。Dan说∶除非将BITMAPINFOHEADER 结构的 biHeight 段设为负值,否则起点就位于左下角。但大多情况下biHeight并非负值,所以起点一般也就在左下角了。但这并不是说扫描行的起点等于图象的高度,起点还是等于0,终点才等于高度。也就是说扫描行的编号是从最后一行开始顺序排列为 0,1,2,3 的。

现在,我们已经完成了对一个位图文件的全面分析。如果还有需要讲的话,那么有两点。第一点是关于真彩色位图。已经在上面提到过,真彩色位图是没有颜色表的。这种图的数据部分存放的是实际的RGB颜色。那么如何知道没有颜色表呢?其实很简单,好象上面也已经讲到了,m_BMFileHeader.bfOffBits  将等于 &H36,即54。还有biBitCount
会是大于等于16位。


第二点,是一个很讨厌的规定。我们把位图中的一行数据信息叫做扫描线。Windows
规定,每一个扫描线必须结束与一个32位的边界。也就是说,一个扫描线的位长度(按位计算)必须整除于32,或字节长度必须整除于4 。也就是说如果只有8位,那么拿空白的24
位来补充,如果只有48位,那么也就拿空白的16位来补充。我们可以证实一下∶

在刚才的图片中,我们已经知道图象的宽度为361个像素点,高度为262个像素点。那么数据区的总的像素点个数为361×262=94582。本例中一个字节代表一个像素,但字节个数并不等于94582。正如已经看到的那样,是为biSizeImage = 95368。还缺少786个字节。这说明一些位给补上了。总共是262行,因此说明每行补充了786÷262=3个字节,即24位。为什么每行补充了24位呢?现在看来只有一种可能。也就是,32位等于是4个字节,因此一行361个字节除以4以后,必须剩下1,Windows才补充了不足的3 个字节。呵呵,361 MOD 4 = 1 @@~

计算DIB各行中的字节数(扫描线长度)的VB代码总结如下∶

imageWidth  -->位图的宽度(像素为单位)
 bmBits      -->每像素的位个数


Function GetLineWidth(bmBits As Integer, imageWidth As Long) As Long

   GetLineWidth = imageWidth

   Select Case bmBits

      Case 1

          GetLineWidth = (GetLineWidth + 7) / 8

      Case 4

          GetLineWidth = (GetLineWidth + 1) / 2

      Case 8

      Case 16

          GetLineWidth = GetLineWidth * 2

      Case 24

          GetLineWidth = GetLineWidth * 3

      Case 32

          GetLineWidth = GetLineWidth * 4

      Case Else

          GetLineWidth = 0     'error

          Exit Function

   End Select

      If (GetLineWidth And 3&) > 0 Then _

          GetLineWidth = (GetLineWidth And &HFFFFFFFC) + 4

End Function

以下给出了与设备无关位图有关的函数

函    数                                      说    明
CreateDIBitmap            在一幅DIB的基础上创建与设备有关位图
CreateDIBSection         创建一个DIBSection对象(一种新的位图对象,和普通设备相关位图相似,但也能在内存中指定一个缓冲区,用以保存DIB格式的位图数据。具体内容已超出本讲座的范围,读者自己参考有关资料)
CreateDIBColorTalbe   为DIBSection对象取得颜色表信息。
GetDIBits                     在与设备无关位图里载如来自一幅与设备有关位图的数据。很不错的函数。
SetDIBColorTalbe        为DIBSection设置颜色表信息。
SetDIBits                     在与设备相关位图里用来自一个DIB的数据设置图象。
SetDIBitsToDevice       将来自DIB的数据直接设到一个设备。可用它将数据从DIB直接传给屏幕或打印机。
StretchDIBits               将来自DIB的数据设到设备场景,同时根据需要伸缩图象。有些设备支持用这个函数将数据直接设到输出设备。可用GetDeviceCaps函数判断具体是否支持。常用函数之一。
       好了,大家稍微休息以下,下一节我们学习与设备有关的位图。

三、与设备有关的位图(DDB)

      与设备有关的位图,相对与与设备无关位图来讲,比较简单。只所以简单是因为我们根本没有必要去学习它的结构。因为,与设备有关的位图的格式总是与其设备场景(顺便说一下,很多书把设备场景叫做“设备上下文”)有关。但2色位图除外。如果你正坐在计算机前,不要认为计算机中只存在一种与设备有关的位图,其实不然。所谓设备并不是指一台计算机,内存,显示缓存,打印机,等都有各自的位图数据格式。不要对与设备有关的位图进行对其结构的任何猜测。你见过我吗?没有吧?但你是否觉得与我交往仍然是很有效呢?我们可以通过Email,因特网进行交流。你就把我理解为一个人,一个远方的朋友就可以了,是不是?对与设备有关的位图也就这样想就可以了。它的确是存在着的,就在计算机的某个设备内部(比如内存,打印机)。当然我们不能通过Email跟它交往,而是要靠函数,范围包括创建,获取信息,设置信息,销毁等等。用惯了你会发现,这也很得劲儿,其难度也就相当与给我发一个E-Mail── 填写需要的地址,需要的标题和内容,然后发送。当然,这里要做的是为一个函数的调用为其添写需要的参数。如果你真的想见它一面,我倒有个方法,是家传秘方呦!好好听,你可以拿锤子把你的计算机用力,请记住一定要用力,用力砸一下,然后打开机箱看看它出来了没有,一般成功率能达到99%。但由于存在那1%的失败的可能,我得先声明如果见不到后果自负∶)

      使用与设备无关位图的过程当中始终不能忘记的一点是,确保位图与设备的兼容性。你不能把一个内存设备场景中的位图直接选入到打印机设备场景,会出现异常或错误。这里所说的设备其实就是指设备场景。为了创建一个与指定设备场景兼容的设备相关位图,可以使用CreateBitmap或者CreateCompatibleBitmap。但两个函数的功能是不同的。我的经验是,在创建一个Mask(掩模位图)图象的时候使用CreateBitmap,而创建一个彩色位图的时候使用CreateCompatibleBitmap。还有几个函数,如CreateDIBitmap,它可以根据一个DIB(设备无关位图)的基础上创建一个与设备相关位图。反正,你应该注意到,几乎所有的与设备相关位图有关的API函数都具有一个hDC参数,有时候看起来没有必要的,但应当清楚这主要是为了保证设备的兼容性而给出的。说明与设备有关位图的有些信息是记录在设备场景中的。

      作为GDI对象,设备相关位图被创建后,需要把它选入到设备场景,这和向设备场景选入一个画笔完全一样,可以用SelectObject函数,只是在先前要写画笔句柄的参数位置上写下位图句柄即可。当你不再使用一个被你创建的位图的时候,应当用DeleteObject函数删除之,以释放系统资源。为了删除它,你还需要用SelectObject函数把原来的位图选回到设备场景,这样你为设备场景选入过的位图就退了回来,处于可以删除或重新被选入的状态。这需要你在为设备场景选入一个你自己创建的位图时保存旧的(原先的)位图句柄。反正这和操纵画笔或画刷之类的GDI对象完全一样。同样,不可将一个位图同时选入两个不同的设备场景。

      创建和使用位图的常用技巧是,创建一个DC或多个DC以及相应的位图,然后在后台(用户看不到)进行各种光栅运算和加工处理后,最后把形成的图象一次性发送到关联设备场景中(关联设备场景中的图象会自动影射到视频内存,如PictureBox和hDC所代表的内存。详细情况请参考前期教程设备场景部分)中(常用BitBlt),以获取速度和最大限度地避免屏幕闪烁(后面给出了例子)。这主要是因为向屏幕输出图象的函数执行速度比较慢,而且图象的加工过程对用户来说没有必要看到的。为了创建若干个设备场景,你需要多次用CreateCompatibleDc函数(这也是最简单的方法),它可以使你获得一个与指定设备场景兼容的设备场景。反正你应该保证创建位图中所使用的hDC参数和创建设备场景中所使用的hDC参数保持一致。别忘了最后用DeleteDC来删除你自己创建的设备场景。在删除设备场景前,应当从该设备场景中抽出为其选入的位图,并将其销毁。以上这种技巧在一本VC书上叫做“双缓冲”,意识是把操作分成前台和后台两部分同时进行。

      接下来给出与设备有关位图的函数吧,请您不要客气随便看。

                               与设备有关位图函数

函    数                                           说    明
CreateBitmap                       创建一幅位图,并选择性地初始化位图数据
CreateBitmapIndirect           在一个BITMAP数据结构的基础上创建一幅位图
CreateCompatibleBitmap     创建与指定设备场景兼容的一幅位图
GetBitmapBits                      获取位图的像素位数据
GetBitmapDimensionEx        取得一幅位图的大小
LoadBitmap                         从资源文件中载入一幅固有的系统位图
LoadImage                          一个常规用途的函数,用于装载图象、图标及指针
SetBitmapBits                      根据一个数据缓冲区设置位图图象。使用GetDIBits吧,更好用。
SetBitmapDimensionEx        设置位图的大小。                  

                          位图传输函数

函    数                                说    明
BitBlt                       位块传输。将一个图象区域传到另一个图象区域。必须掌握到熟背。
PatBlt                      图案块传输。根据指定图案(就象由一个刷子表示的那样)填充一个图象区域。
GetStretchBltMode  进行伸缩处理时,用于判断Windows删除线段或像素方式。
MaskBlt                   执行复杂的图象传输,同时进行掩模(MASK)处理
PlgBlt                      平行四边形块传输。允许我们伸缩、扭曲及放置一幅图象。这个函数我在Windows95中用过,可始终没有成功。功能看起来很不错。
SetStretchBltMode  进行伸缩处理时,用于决定Windows删除线段或像素的方式。
StretchBlt                将一幅位图从一个设备场景复制到另一个。源和目标DC相互间必须兼容。这个函数会在设备场景中定义一个目标矩形,并在位图中定义一个源图象。源矩形会根据需要进行伸缩,以便与目标矩形的大小相符。
     DIB并不存在于设备场景中(device context)。在它被选入设备场景或在它被画入设备场景前,DIB必须被翻译为DDB。你可以用StretchDIBits函数完成这一工作。本教程提供了一个源代码例程。通过它你能够学习DDB到DIB,再从DIB到DDB的转化过程。程序名为vbplay46.vbp。实际上是源码解析中的演示程序第46号。


      为了使用好位块传输,你需要掌握图形光栅方面的知识。其内容和上一期给出的位操作运算在其原理上相同。只是需要进行的并不是一个、两个或四个直接等的数据,而是一个位图数据。位图数据也是以字节来构成的,只是长度要长得多。为了使用光栅运算,你需要有一个自己的表格,以便从中查找并计划光栅方案。以下是Dan的书中的,也是我最喜欢的一个表(详细的中文说明格式的表不如它好用)∶

SRCCOPY       Destination = Source

SRCPAINT      Destination = Source OR Destination

SRCAND        Destination = Source AND Destination

SRCINVERT     Destination = Source XOR Destination

SRCERASE      Destination = Source AND (NOT Destination)

NOTSRCCOPY    Destination = NOT Source

NOTSRCERASE   Destination = (NOT Source) AND (NOT Destination)

MERGECOPY     Destination = Source AND Pattern

MERGEPAINT    Destination = (NOT Source) OR Destination

PATCOPY       Destination = Pattern

PATPAINT      Destination = (NOT Source) OR Pattern OR Destination

PATINVERT     Destination = Pattern XOR Destination

DSTINVERT     Destination = NOT Destination

BLACKNESS     Destination = 0

WHITENESS     Destination = All bits set to 1

       可惜,少一个很重要的。可别担心,本老师有办法∶

&H220326     Destination=(NOT Source) AND Detination

注∶以上,Destination指的是目标位图(BitBlt中前一个DC中的),Source是来源位图(BitBlt中的后一个DC中的)。

       以下给出一个例子,是我常用的一个函数。它用来实现透明复制位图。你可以在你的程序中直接粘贴使用。它总结了本节给出的内容。

作者(xing)  1999年10月17日 整理备用

功能∶

  透明复制位图

参数表∶

  hDestDC --------  Long,目标设备场景

  x,y ------------  Long,对目标DC中目标矩形左上角位置进行描述的那个点。用目标DC的逻辑坐标表示

  nWidth,nHeight -  Long,欲传输图象的宽度和高度

  hSrcDC ---------  Long,源设备场景。如光栅运算未指定源,则应设为0

  xSrc,ySrc ------  Long,对源DC中源矩形左上角位置进行描述的那个点。用源DC的逻辑坐标表示

  TransColor -----  OLE_COLOR,被透明处理的颜色

Sub TransBlt(ByVal hDestDC As Long, ByVal x As Long, ByVal y As Long, ByVal nWidth As Long,                 ByVal nHeight As Long, ByVal hSrcDC As Long, ByVal xSrc As Long, ByVal ySrc As Long, ByVal TransColor As OLE_COLOR)

       Dim dl              As Long

      

       Dim OrigColor       As Long

       Dim OrigMode        As Long

      

       Dim saveDC          As Long

       Dim maskDC          As Long

       Dim invDC           As Long

       Dim resultDC        As Long

      

       Dim hSaveBmp        As Long

       Dim hMaskBmp        As Long

       Dim hInvBmp         As Long

       Dim hResultBmp      As Long

      

       Dim hSavePrevBmp    As Long

       Dim hMaskPrevBmp    As Long

       Dim hInvPrevBmp     As Long

       Dim hDestPrevBmp    As Long

      

       saveDC = CreateCompatibleDC(hDestDC)

       maskDC = CreateCompatibleDC(hDestDC)

       invDC = CreateCompatibleDC(hDestDC)

       resultDC = CreateCompatibleDC(hDestDC)

      

       '按照规定的格式创建一幅与设备有关位图

       hMaskBmp = CreateBitmap(nWidth, nHeight, 1, 1, ByVal 0&)

       hInvBmp = CreateBitmap(nWidth, nHeight, 1, 1, ByVal 0&)

   

       '创建一幅与设备有关位图

       hResultBmp = CreateCompatibleBitmap(hDestDC, nWidth, nHeight)

       hSaveBmp = CreateCompatibleBitmap(hDestDC, nWidth, nHeight)

      

       hSavePrevBmp = SelectObject(saveDC, hSaveBmp)

       hMaskPrevBmp = SelectObject(maskDC, hMaskBmp)

       hInvPrevBmp = SelectObject(invDC, hInvBmp)

       hDestPrevBmp = SelectObject(resultDC, hResultBmp)

      

       '产生Mask图象

       OrigColor = SetBkColor(hSrcDC, TransColor)

       dl& = BitBlt(maskDC, 0, 0, nWidth, nHeight, hSrcDC, xSrc, ySrc, vbSrcCopy)      

       TransColor = SetBkColor(hSrcDC, OrigColor)

       'invDC的图象将与maskDC图象相反

       dl& = BitBlt(invDC, 0, 0, nWidth, nHeight, maskDC, 0, 0, vbNotSrcCopy)

       'resultDC的图象将成为被写位置的图象

       dl& = BitBlt(resultDC, 0, 0, nWidth, nHeight, hDestDC, x, y, vbSrcCopy)

       'resultDC中,需要新写的位置将变为黑色

       dl& = BitBlt(resultDC, 0, 0, nWidth, nHeight, maskDC, 0, 0, vbSrcAnd)

       dl& = BitBlt(saveDC, 0, 0, nWidth, nHeight, hSrcDC, xSrc, ySrc, vbSrcCopy)

       'resultDC中不被写入的颜色成为黑色

       dl& = BitBlt(saveDC, 0, 0, nWidth, nHeight, invDC, 0, 0, vbSrcAnd)

       '将两幅图合并起来

       dl& = BitBlt(resultDC, 0, 0, nWidth, nHeight, saveDC, 0, 0, vbSrcInvert)

       '完工后输出

       dl& = BitBlt(hDestDC, x, y, nWidth, nHeight, resultDC, 0, 0, vbSrcCopy)

      

       SelectObject saveDC, hSavePrevBmp

       SelectObject resultDC, hDestPrevBmp

       SelectObject maskDC, hMaskPrevBmp

       SelectObject invDC, hInvPrevBmp

      

       DeleteObject hSaveBmp

       DeleteObject hMaskBmp

       DeleteObject hInvBmp

       DeleteObject hResultBmp

      

       DeleteDC saveDC

       DeleteDC maskDC

       DeleteDC invDC

       DeleteDC resultDC

End Sub

函数需要的API

Private Declare Function BitBlt& Lib "gdi32" (ByVal hDestDC As Long, ByVal x As Long,        ByVal y As Long, ByVal nWidth As Long, ByVal nHeight As Long, ByVal hSrcDC As Long,         ByVal xSrc As Long, ByVal ySrc As Long, ByVal dwRop As Long)

Private Declare Function CreateCompatibleBitmap& Lib "gdi32" (ByVal hdc As Long,        ByVal nWidth As Long, ByVal nHeight As Long)

Private Declare Function CreateCompatibleDC& Lib "gdi32" (ByVal hdc As Long)

Private Declare Function DeleteObject& Lib "gdi32" (ByVal hObject As Long)

Private Declare Function SelectObject& Lib "gdi32" (ByVal hdc As Long, ByVal hObject As Long)

Private Declare Function SetBkColor& Lib "gdi32" (ByVal hdc As Long, ByVal crColor As Long)

Private Declare Function CreateBitmap& Lib "gdi32" ( ByVal nWidth As Long, ByVal nHeight As Long, ByVal nPlanes As Long, ByVal nBitCount As Long, lpBits As Any)  

 四、关于ImageIo.dll

      在前一段时间,有很多朋友来信问我如何把BMP图象或图片框中的图象保存为JPG或GIF格式文件。我还没有回答过一次。因为我始终没有找到称心的DLL(动态连接库)。最近3个月,我一直学了VC,也算是勉强入门。我发现VC界的很多人都在使用一个叫ImageLoad.dll的动态连接库来完成这些工作。可惜,这个动态连接库到VB就不好使了。因为其函数使用了指针(而不是句柄)。然而,可但是,只要本老师在,你就可以用这个动态连接库了。本老师写了一个叫ImageIo.dll的动态连接库,用VC编写的。此连接库完全面对VB编写的,自然参数上完全考虑了VB用户。该连接库可以把你在VB中传送的句柄适当地改成指针并再调用ImageLoad.dll这个动态连接库,然后把处理结果以VB能够接受的方式返回给VB用户。另外一点是,ImageLoad.dll的函数比较低级的,用起来也不方面。为此,我在ImageIo.dll中写了一些代码,使得使用起来更加方便,更加可靠。

      然而,也出现了一个接着一个的让我伤心的事情。第一个是,函数不能读取部分GIF格式的文件(有些可以读出,但不是动画,只有一张静态画面);另一个是无法存成GIF格式,最后一点就古怪了,当像素位不等于24的时候位图文件不能存成JPG格式。该连接库可以操纵6中格式文件,有BMP,GIF,JPG,PCX,TGA,TIF。我对TGA和TIF没有怎么考虑过,因为在GIF中出现一些问题后感到这个ImageIo.dll不能使其功能齐全了,而且TGA和TIF也并不是常用文件,也就不大测试了。那么,是因为我还不懂VC吗?不,并不是我写错了代码,而是该动态连接库提供的演示程序同样不能解决我遇到的问题。据图象处理方面的书上说,一个JPG库代码,需要一个人用半年的时间才能写完。我总不能花半年去写一个这种库文件吧。其实我手里有一个,而我却懒得去用它。

      我考虑了,你能拿ImageIo.dll能做什么?想编写一个图象处理软件?如果你这样想的话,那么我建议你不要用它,如果你真的想编一个图象处理软件,最好还是自己从头编写一个完整的DLL文件。我想,算是出了著名的图象处理软件的公司都是如此。而且我建议你不要用VB来编写这种软件,不适合。我想,对绝大部分VB届的朋友来讲,对JPG的需求无疑是将一些图象文件压缩起来,以节约磁盘空间。比如,你需要编写一个人事档案管理系统,把人员照片扫描后保存成JGP文件。那么我想,你用这个ImageIo.dll文件,算是很不错的。我曾经在网络上下载过一个DLL文件,但其压缩质量实在差,而ImageLoad.dll却做得很出色。

   你在本教程的打包文件中可以找到一个Test.vbp文件,正是对这个ImageIo.dll写的。有关ImageIo.dll的技术文档,现给出如下,欢迎提一些宝贵意见∶


ImageIo.dll

      本动态连接库是为了解决VB用户不能使用ImageLoad.dll动态连接库文件而编写的。它可以作为一个桥梁,使VB用户能够使用ImageLoad.dll。但它并不是对ImageLoad.dll的简单转化,而是添加了诸多代码,使其用起来更加简单、方便。它可以帮助用户完成 Bmp,Gif,Jpg 等多种图象文件的数据的读取和存盘工作。

制作者∶Xing

编程环境∶VC6.0

日期∶1999,12,20

常量∶

1∶文件格式

Const IMAGETYPE_NONE = 0     '未知格式

Const IMAGETYPE_BMP = 1      'bmp 文件格式

Const IMAGETYPE_GIF = 2      'gif 文件格式

Const IMAGETYPE_PCX = 3      'pcx 文件格式

Const IMAGETYPE_TGA = 4      'tga 文件格式 

Const IMAGETYPE_JPG = 5      'jpg 文件格式

Const IMAGETYPE_TIF = 6      'tif 文件格式

Const IMAGETYPE_FIRSTTYPE = IMAGETYPE_BMP     '第一个文件格式   (bmp) = 1

Const IMAGETYPE_LASTTYPE = IMAGETYPE_TIF      '最后一个文件格式 (tif) = 6

函数∶

GetImageType

VB∶

   Private Declare Function GetImageType Lib "ImageIo.dll" (lpsFilename As String) As Long

VC∶

   __API int GetImageType(LPTSTR & pszFilename);

说 明∶

    获取图形文件类型

返回值∶

    文件类型 0--6,0 表示出错或未知文件类型

参数∶

    lpsFilename  String 文件名

ImageLoad

VB∶

    Declare Function ImageLoad Lib "ImageIo.dll" (lpsFilename As String, ByVal hDC As Long, ByVal nX As Long, ByVal nY As Long) As Long

VC∶

   _API HGLOBAL ImageLoad( LPTSTR & pszFilename,HDC hDC, int nX, int nY);

说明∶

    装载一个图象文件,并必要时输出到指定的DC

返回∶

    返回指向装有图象文件数据的一个缓冲区的指针。不管你打开任何文件,装载到缓冲区的数据都已经是转换为DIB格式,也就是说和BMP文件一样。在不在使用该缓冲区的时候,应该用API函数GlobalFree或者本动态连接库提供的KillImage函数释放掉。

参数∶

KillImage:

VB∶

    Declare Function KillImage Lib "ImageIo.dll" (ByVal hDib As Long) As Long

VC∶

    __API HGLOBAL KillImage(HGLOBAL hDib);

说明∶

    本函数与API函数GlobalFree在其功能上完全一样,当然可以改用GlobalFree 。VC代码如下∶

HGLOBAL CImageIoApp::KillImage(HGLOBAL hDib)

{
 return(GlobalFree(hDib));
}
注∶如果成功返回0,否则返回hDib。

ImageDraw:
VB∶
    Declare Function ImageDraw Lib "ImageIo.dll" (ByVal hDib As Long, ByVal hDC As Long, ByVal nX As Long, ByVal nY As Long) As Long
VC∶
    __API BOOL ImageDraw(HGLOBAL hDib,HDC hDC, int nX, int nY );
说明∶
   将图象输出到指定DC。
返回值∶
   成功返回1,否则返回0。
参数∶
   hDib 一个指向缓冲区的指针。该缓冲区中装有DIB位图数据。可以用LoadImage函数获取它。也可以自己创建。

   hDC 需要绘制图象的设备常场景句柄
   nX,nY 设备场景中被绘制图象的左上角的坐标。

GetInfo:
VB:
    Declare Function GetInfo Lib "ImageIo.dll" (ByVal hDib As Long, BIH As BITMAPINFOHEADER) As Long
VC:
    __API BOOL GetInfo(HGLOBAL hDib,BITMAPINFOHEADER & BIH);
说明∶
   本来是想用GetImageInfo作为函数名,但VC中用此名发生一些冲突,改用了GetInfo。此函数用于获取DIB文件中的BITMAPINFOHEADER结构信息。
返回值∶
   成功返回1,失败返回0。

参数∶
   hDib 一个指向缓冲区的指针。该缓冲区中装有DIB位图数据。
   BIH 一个准备装载信息的BITMAPINFOHEADER结构。传递前没有必要设置其biSize(结构长度)成员变量。


__API BOOL ImageSave(LPTSTR & pszFilename,HGLOBAL hDib,int nType,int nQuality);

只适用于BMP,JPG,TGA

五、图表与指针

       图表是一幅小位图,图表文件的常用附加名是ICO。它的一项特殊能力就是不仅允许任何像素使用某种位图颜色,也允许它们使用屏幕或反转的屏幕颜色。Windows3.x中的图表通常是32×32像素大小,但在Windows95(98)和NT4.0中,对16×16和64×64像素图标的支持已非常普遍了。图标实际包含两幅独立位图。第一幅位图可能是单色的或彩色的,其中包含了要与显示屏幕图象合并到一起的一幅图象。这种合并是通过异或(XOR)操作实现的。我们将其称为XOR位图,其中包含了一个掩模。在显示屏幕图象与XOR位图合并到一起之前,这个掩模会先与屏幕图象进行AND操作,然后才是XOR位图和屏幕进行XOR。

      鼠标指针在其内部结构上和图表完全一致,只是它多出一个斎鹊銛,即该指针位图中代表指针位置的准确坐标点。该文件的附加名一般是CUR。能对图标进行操作的大部分函数也能对(鼠标)指针。

      图标被装入到内存之后将产生一个句柄。但需要注意的是,图表并不属于GDI对象,而是属于USER对象。所以有可能在应用程序间共享图表资源。


    以下给出了有关图标和指针的API函数

函    数                              说    明
CopyCursor                 复制指针,使用CopyIcon
CopyIcon                     复制图表。
CreateCursor              创建指针
CreateIcon                 创建图表
CreateIconIndirect      在一个ICONINFO结构的基础上创建一个图标
DestroyCursor            清除指针
DestroyIcon               清除图表
DrawIcon                    向指定设备场景绘制图标
DrawIconEx                用附加的选项描绘图标
ExtractIcon                 从一个可执行文件或DLL中载入图标
ExtractAssociatedIcon 载入指定文件内部或与它相关的一个图标
GetIconInfo                 取得关于图标的信息
LoadCursor                 从一个资源文件中载入指针,或者装载一个固有系统指针。
LoadCursorFromFile    通过读取标准指针文件(.cur)或动画指针(.ani)创建指针
LoadIcon                    从一个资源文件中载入图表,或载入一个固有系统图标。

六、颜色区间  

     RGB
     我们已经知道IBM兼容视频系统上的颜色由RGB三色体现。RGB三色中的每一种颜色都包含颜色中的红色、绿色和蓝色成分的值。这三部分的值组合在一起确定屏幕上所显示的颜色。RGB是常见颜色空间,红色、绿色和蓝色被认为是基本色,不能够被进一步的分解。而色彩系统可以分为两类,加法色彩系统和减法色彩系统。加法色彩系统,例如RGB系统中的颜色可通过将颜色添加到黑色中创建新颜色。添加的颜色越多,结果颜色也就越趋向于白色。足够的主要颜色可以创建出纯白色,而缺少所有主要颜色只能得出纯黑色。

   
      一个RGB是用四个字节来构成的。好象该字节中除了表示红、绿、蓝三个字节以外的剩余一个字节是多余的,其实不然。从一个API绘图函数引用一种颜色,都不只是用容纳红、绿、蓝颜色值的三个字节,而且还用第四个字节(最高字节)。这个字节包含一个标志值。顺序排列的最低字节包含红色值,其次两个字节分别是绿色和蓝色,而最后位字节则包含一个标志。此标志用于指示是否引用一个高频脉动色,或者一个调色板匹配色,或者一个明显的调色板索引。这个字节的值决定顺序排列的三个低位字节如何选择一种颜色。当你想指定一个建立这些对象的API函数中的笔或刷子的颜色时,可以把高位字节设置为如下表中的三个值之一。

高字节值           结果
&H00              在对象绘出后,Windows高频脉动20个保留色,这叫RGB颜色引用。(普通情况)
&H01              最低字节不是红色的值,而是指一个调色板项的数目或索引值,因此Windows使用在那个调色板项中的颜色,中间的两个字节(字节1和2,原绿色和蓝色字节)应保持值&H00,这叫调色板索引引用。
&H02              Windows定位与由红、绿、蓝决定的在三个顺序排列的低位字节指定的颜色最相匹配的调色板项。这叫作调色板RGB引用。
因此,严密地讲,一个RGB值是通过以下方式构成的。

Dim RGBColor As Long

RGBColor=RedValue + (GreenValue * 256) + (BlueValue  * 65536) + _

         (FlagValue * 16777216)

    这种技术的使用请参考本教程附带的程序Palette.vbp。  

      CMY
      在计算机领域还存在几种RGB颜色区间的变种。一个是叫做CMY的颜色区间。指的是蓝绿色、紫红色以及黄色。打印机和照相机通过加墨乳剂渲染颜色时,采用的是减法色彩系统。它由将颜色色素沉淀到白色纸上的大部分硬拷贝设备使用,例如激光打印机和喷墨打印机。当被现实时,这三种颜色都吸收与自身互补的浅颜色。蓝绿色吸收红色,紫红色吸收绿色,黄色吸收蓝色。例如,可增加黄色墨的量,图象中的蓝色量减少。RGB模式和
      CMY模式之间的转换是非常简单的。为了计算蓝绿色,请从255中减去RGB中的红色值;对于紫红色,则从255中减去RGB中的绿色值;至于黄色嘛,喔!你已经猜测到了,即从255中减去RGB中的蓝色值。例如,RGB(240,12,135)的CMY值分别是15,243和120。然而,这种颜色区间大概并不是我们所感兴趣的,当然也有人感兴趣的。但下面给出的颜色区间更吸引我们,起码是我。

      HSV(颜色,浓度,亮度)
      HSV是众多色彩系统中的一个。如果你打开了通用对话框的颜色对话框家会发现,除了RGB调解器,还有一个类似的但其值很古怪的调解器。很多3D动画软件和图象处理软件中,这种HSV颜色空间也被广泛采用。它的基本原理是更改颜色属性值以创建新颜色,而不是使用颜色本身的混合色。色调般指颜色,例如红色、桔黄色、蓝色等等。饱和度(也被称为浓度)指示指示色调中的白色量∶全饱和和色调不包含白色,显示纯色。而部分包含色调根据所混合的白色的情况,显示的颜色浅。例如,50%的饱和度的红色色调显示为粉红色。值(也被称为亮度)是颜色自身的发光度,也就是它所发出的光的多少。高饱和度色调非常亮,而低饱和度色调非常暗。

      HSV与画家以及其他通过将白色、黑色和灰色增加到纯色素中以创建色彩、底纹、以及色调的艺术家们使用的色彩系统非常相似。色彩是纯色,是组合了白色的全饱和度颜色,而底纹是组合了黑色的全饱和度颜色。如果使用这两种颜色混合,那么HSV的饱和度是白色量,值是黑色量,色调是增加了黑白两色后的颜色。
 更详细的情况不再这里说了,说起来也费劲,也不常用,以后做个程序放到站点里。

七、关于调色板的基础知识

      关于调色板,很少书谈得深入,Dan的书上讲得也就那么一点点,少得实在可怜,连一个有关调色板的API都没有给出。为了弄懂它,我也付出了不少时间。当然,现在就给你讲解讲解。把各个角落中学来的知识总结在这里,想来也是满不错的主意感兴趣的朋友可以看,不看也无访。。

      用户显示于简报的图片很可能需要大量不同的颜色。可以想象∶艳绿色可滚动的爱尔兰小山的照片会比火星的图片需要更多的套不同的颜色、若用户想同时显示这两幅图象,会碰到一个很现实的问题。即使不限制逼真度,甚至用256种颜色,用户都不可能有足够的浓淡级别同时提供给两个图象。值得庆幸的是,若一次只有一幅图片显示,用户可以要求Windows交换颜色,使每个图象能激活自己的256种颜色选择--它自己的颜色调色板。

        那么,什么是调色板?它是一种Windows对象,但适当地,如果你把它想象成一种特定长度的结构数组,对理解会有帮助的。还记得RECT结构吧,如果声明
   
    Dim MyRect(0 to 255) as RECT
   
      以上,我们定义了一个RECT结构数组。当然,所有的笨蛋都知道调色板并不是RECT的。调色板结构比RECT要复杂一点。首先调色板是一种叫LOGPALETTE 的结构来构成的。如下∶

    Type LOGPALETTE
         palVersion          As Integer    
         palNumEntries       As Integer    
         palPalEntry ( 1 )   As PALETTEENTRY

    End Type

        palVersion 指Windows的版本号,数值300代表Windows3.0或3.1,不过请记住,在Windows9X中仍然使用该数值,即300。第二个参数值调色板登录项的数目。也就是说,它指的是第三个参数palPalEntry的元素个数。这个例子中显然是1喽!那么在以下的情况呢?

    Type LOGPALETTE
         palVersion          As Integer    
         palNumEntries       As Integer    
         palPalEntry ( 255 )   As PALETTEENTRY
    End Type
   
        当然是256。但这也不一定,比255小也无妨。比如在4位模式中,应当是16,就算你声明为palPalEntry ( 255 ),创建调色板时,函数只使用其前面的0到15的数组元素。

        接下来,正如你已经看到的那样,第三个参数是一种结构数组。当然不是RECT喽,而是 PALETTEENTRY。它的内容如下∶

    Type PALETTEENTRY
         peRed               As Byte      
         peGreen             As Byte      
         peBlue              As Byte      
         peFlags             As Byte      
    End Type

       Red,Green,Blue,不用我说了吧?最后一个peFlags是什么呢? 它是包含用来描述调色板项类型的一个或多个颜色标志的值。如下表显示了个些标志∶

标志                           值                        说     明
PC_EXPLICIT           &H2      创建一个调色板条目,该调色板条目在系统调色板中指定一个索引而不是颜色。有显示系统调色板内容的程序使用。
PC_NOCOLLAPSE    &H4      创建一个被影射到系统调色板中未用条目上的调色板条目,即使该颜色条目已经存在。用于当两个条目影射到同一颜色上时确保调色板颜色的唯一性。
PC_RESERVED         &H1      创建一个某应用程序专有的调色板条目。当PC_RESERVED条目被添加到系统调色板时,它不被影射到其他逻辑调色板中的颜色上,即使这些颜色匹配。由执行调色板动画的程序使用。
   

      在8位显示器上,一个像素的颜色是通过查看一个色彩表中自己的8位像素值来决定的。一个调色板包含一套(数组)24位RGB色彩值(peRed,peGreen,peBlue)。一个调色板中的颜色数目最多256个(从0到255)。每个显示内存的像素选项包含一个从0到255的值,这个像素指定哪一个调色板项被用来为这个像素着色。要改变一个像素的颜色,用户有两个选择∶其一,可改变这个像素的颜色索引值;其二,改变调色板登录项上的颜色值(RGB值,24位)。在后一种情况下,这种改变使得所有引用这个调色板登录项的像素均同时引起颜色变化。


      然而你应当清楚,初始化一个LOGPALETTE变量,并不能说它是一个调色板。因为调色板是GDI对象的一员,就像画笔、画刷之类的GDI对象一样,必须通过特定的函数创建它。创建后,我们可以获得一个调色板句柄,那么这个句柄所代表的就是真正的调色板了。

      每个用户显示的图象能带自身的色彩调色板。另外,每个活动窗口能根据自身目的来操作调色板。但是,记住,应用于全屏幕,具有256种颜色的限制,这并不是对于每个窗口或应用程序而言。对于这些,还得用Windows调色板管理器来进行管理。Windows调色板管理器决定在给定时间哪个窗口拥有调色板控制。在前台活动的窗口常常拥有优先权。若此窗口不使用调色板,优先权则传给Z向(Z-order,听说过吧?它垂直于屏幕。因为我们常把屏幕坐标用X和Y来表示。而三维坐标是用X,Y,Z来表示的。这里指的Z并不是说真的三维坐标中的那个Z轴,而只是一个形象的比喻中被命名的,是指桌面窗口垂直方向)的下一个窗口。一旦带最高优先权的窗口认清自己的调色板作为前台调色板,其他窗口将被依次通知为背景调色板,这是由调色板管理器来通知。在一般情况下调色板管理器是自动工作的。

       刚才我说了一句“认清”这么一个单词。那么,什么叫“认清一个调色板”呢?每个用户显示的图象能带自己的调色板(几个调色板能被同时放在内存中)。存放在内存的调色板叫做逻辑调色板。在用户显示系统的调色板决定着哪一种颜色实际出现在屏幕上,这个调色板被叫做系统调色板或者硬件调色板(集中精力!系统调色板是硬件层的)只有一个硬件调色板并且调色板管理器保留一个它的复制调色板。当一个应用程序激活自己的颜色时,它必须在设备场景(device context)中选入逻辑调色板并认清它。这意味着,应用程序必须要求调色板管理器装载它的逻辑调色板给系统(硬件)调色板。所以,所谓“认清”实际上是一种“拜托”,或者就象日本人常说的“多多关照”∶请多多关照,使用我的调色板吧。因为只有这样,应用程序才能显示出符合自己口味的颜色。

      由于调色板的大小会发生改变,调色板管理器不会傻呵呵地硬着从逻辑调色板复制一个固定大小的256种颜色元素的块给硬件调色板。而是,这个调色板管理器只装载它在每个逻辑调色板中找到的那么多的颜色。系统调色板可以容纳多个逻辑调色板,只要颜色总数不超过256即可。Windows为它静态颜色(用于画按钮、边框、文本、图表等等,叫做系统保留色。可以用GetSystemColor函数获取的那几种)保留了20种调色板登录项。因此,只为我们剩下236个可变颜色位置。但这并不意味着一个调色板将只包括它需要的236种颜色来支持它的位图。把8位图象调色板转化为Windows本体调色板是明智的。Windows本体调色板包括20种保留色,特别是用户打算用调色板绘图时,否则不能用保留的颜色。本教程附带的Palette.vbp程序如果在16位或24位模式下运行,将只显示这20种颜色。若用户愿意,可以用系统静态颜色把可定义的颜色范围扩展到256。GDI就是为这个目的提供了特殊函数。但这将干扰其他活动程序的基本命令,因为它将动态改变活动程序的外观。因此除非你的程序独占整个屏幕(比如一些游戏就是这样的,不与其他窗口程序同时显示),否则这种做法是不应采取的。

      一个逻辑调色板中的颜色在系统调色板中一般不占据与在逻辑调色板中相同的位置。这是因为在认清一个调色板的时候,调色板管理器建立一个交叉引用表,也叫做调色板映像(palette mapping),正如它把一个逻辑调色板加载给系统调色板。这个表用于GDI绘图函数把像素从逻辑调色板翻译到系统调色板索引。很好理解的。可以把它想象为一种两列的表,左列中填写有逻辑调色板的颜色索引值,对应的右列中填写有系统调色板的索引值。就像英语词典里一个英语单词对应着一个中文单词。记住,计算机中所谓映像都是指这种情况。比如消息映像,它为各种消息值调用相应的消息处理函数。接着来。正如前面所解释的,一个像素的颜色是通过在颜色表中查看它的值来决定的。在设备相关位图和DIB(设备无关位图)的情况下,这是256种颜色位图文件的最通用窗体,组成位图像素数据的字节包含在文件的颜色表中的登录项的值。当GDI把图象从文件转移到屏幕时,也就是说,从一个设备相关位图转移到另一个依赖于设备的位图或DDB(设备相关位图)时,它用调色板映像来改变像素值,使它能引用系统调色板中当前正确的颜色。为一个逻辑调色板建立的调色板映像叫做前台映像。

       若从活动窗口没有获取所有调色板登录项,则剩下的位置被填入从非活动窗口拿来的颜色,直到或者所有的位置占据,或者没有其他的窗口要求识别自己的调色板位置。若前台窗口需要所有的236个自由颜色位置,则所有的非活动窗口必须受前台活动调色板的支配。调色板管理器还能自动地执行这个服务,这是通过把非活动窗口中的颜色映像给当前被辨认出的调色板中的最匹配的颜色来实现的。这就是所谓的后台影像。这有时会产生有趣的但对大多数人来讲是糟糕的结果。比如,最匹配色=很不匹配,其他色=更不匹配的情况下。另外,你需要记住的一点是,当焦点每次从一个基于调色板的应用程序更换到另一个基于调色板的应用程序时,整个辨认过程重复执行一次。

     DIB中的像素包含与位图一起的逻辑调色板中的颜色索引,通常一个颜色表存在于DIB文件中;DDB中的像素包含系统调色板中的颜色索引。当用户是根据索引值来访问一种颜色时,用户是在逻辑调色板中根据它的位置来访问颜色的,而不是在系统调色板中的它的位置。也就是说,你只能用逻辑调色板的索引值。因此,假如逻辑调色板中不存在20中系统颜色,那么我们就无法访问它,这并不是说它不存在。因此,如果你需要系统颜色,就应当把这些颜色值装载的逻辑调色板。

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页