Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1426979
  • 博文数量: 430
  • 博客积分: 9995
  • 博客等级: 中将
  • 技术积分: 4388
  • 用 户 组: 普通用户
  • 注册时间: 2006-05-24 18:04
文章存档

2013年(1)

2008年(2)

2007年(14)

2006年(413)

分类:

2006-10-19 14:08:47

              图像知识

   

 

 

 

 

 

 

 

 

    图形图像

 


传统的电脑只能处理文字、数字,最多是简单的图形。近年来,随着电脑硬件技术的飞速发展和更新,使得计算机处理图形图像的能力大大增强。以前要用大型图形工作站来运行的图形应用软件,或是特殊文件格式的生成及对图形所作的各种复杂的处理和转换;如今,很普遍的家用电脑就完全可以胜任,我们可以轻易的使用PhotoShop、CorelDraw、3D MAX或是别的什么软件做出精美的图片或是逼真的三维物体,你甚至可以自己去做一个有趣的动画。
      在当今信息社会,以多媒体为代表的信息技术和信息产业的发展和应用对人类社会产生的影响和作用愈来愈明显,愈来愈重要。多媒体的发展和应用,极大地推动了诸多工业的相互渗透和飞速发展,逐步改变了整个人类社会的工作结构和生活方式。可以毫不夸张地说,多媒体产业的形成和发展,将不仅引起计算机工业的一次革命,也将影响人类社会发生一场巨大的变革。
       我们知道,所谓多媒体,即多种信息媒介,通常包括以下几种:文本、图形、影像、声音、视频、动画。可以看出,多媒体的应用在很大程度当依赖于丰富多彩的图形和图像。也就是说,图形图像技术的飞速发展也将是必然趋势,掌握图形图像处理技术对一个计算机操作人员是必要的。
        计算机图形学是研究用计算机生成、处理和显示图形的一门科学。为了生成图形,首先要有原始数据或数学模型(如工程人员构思的草图、地形航测数据、飞机的,总体方案模型等),这些数字化的输入信息经过计算机处理后变成图形输出。
        图形从原始数据生成图象数据经过了一系列变换过程,每个变换过程都可能产生不同于输入数据的输出数据,这些数据需要按一定的结构进行组织,形成一系列描述图形数据的文件,我们把这类文件称为图形文件(也称为图形图象文件),而图象文件是描述图象数据的文件,它是图形文件的一种特例。
        在图形生成过程中有多种类型的数据,如模型数据、场景数据和图象数据等,因此,图形文件所描述的图形层次就不一样,这也是产生多种图形文件的一个重要原因。
        另一方面,在同一个描述层上,由于每种图形软件包使用自己的格式保存图形数据,随着图形应用软件包的不断增多,图形文件的格式也会越来越多,虽然国际标准化组织(ISO)为解决图形信息的共享问题,建立了一系列图形文件标准(如CGM),但是这些标准较难得到广大用户和厂商的支持,从而形成了目前这种多种图形文件共存的局面。
图形文件有以下特点:(1)数据量大。由于现在数据获取手段日趋先进,可以得到的数据越来越复杂,数据量也增大。(2)结构性强。数据在本质上分为数字化的和模拟的两种。模拟信息可以转换为数字信息。数字系统中的最基本单位为位(bit),其他结构单位都以位为础。在较低层次上可以是“构造块”(如浮点数、整数和字符);在较高层次上可以是记录(如Pascal中)或结构(如C语言中),而图形文件就是由特定的结构或记录组成的。每种图形文件都按自己的方式组织图形信息,由于图形文件包含的数据量大,所以很多图形文件都使用一定的压缩算法来压缩图形数据。

        上一节从广义上介绍图形图象文件概念,从本节开始将把主要笔墨放在图形图象文件的特例——图象文件上。因为本书主要讨论图象文件的显示与处理技术,所以后面除特殊需要外,一般不使用图形图象文件这一术语,而是使用图象文件这一术语。
图象可粗分为两大类:位映象图象和向量图象。基于计算机的位映象图象是对电视图象的数字化,它易于描述真实景物,真实世界中的景物可以用扫描仪生成图象文件并在计算机上显示。而向量图象易于表达艺术家设计的图形。这两者在表达方式上的不同。
    为简单起见,可把位映象图象看成是一点矩阵(简称点阵)。对于单色位映象图象或打印机输出的图象而言,矩阵中的每个点要么为l要么为0(1代表黑,0、代表白或相反)。在图形学中,把矩阵中的点称为象素(pixel)。
位映象图象根据彩色数分为以下四类:单色图象、具有4~16种彩色的图象、具有32~256色的图象和256色以上的图象。也可把这四类图象称为单色图象、低彩色分辨率图象、中等彩色分辨率图象和高彩色分辨率图象。
在讨论位映象图象的彩色时,通常用保存彩色信息所需的位数来定义彩色数。把单色图象称为是1位图象,这是因为图象中的每个象素仅需1位信息;把16色图象称为是4位彩色图象,这是因为图象中的每个象素需4位信息;要表示16种不同的彩色,象素必须由4位组成,由于4色图象和8色图象不太常用,所以一般也就用不到“2位彩色图象”和“3位彩色图象”。
在PC机上,另一种常见的图象是256色图象,也称8位彩色图象。256色图象有照片效果,比较真实。
另外一种具有全彩色照片表达能力的图象为24位彩色图象。由于彩色的种类很多,每个象素需24位,使得彩色图象所需的存储空间很大。


        最初设计计算机只是为了处理数字和字符,但在近几年,即使是最便宜、最简单的机器,也能够同样便利地以一种形式或另一种形式处理图形,随着真实图形能力的到来,不管是界面的,还是应用程序的;不管是基本的还是高级的,都将引起一场重要的文化变革。目前还很少有人能欣赏这种变革,部分原因是很少有人接受过训练,也很少有人熟悉有效地使用这种强有力的、从本质上来说完全不同的可视媒体的方法。
        计算机把图形显示为一组二维的点,这些点叫象素,象素(Pixel)曾经是“pictiure element”的缩写,之后,它依靠自身的作用而成为一个独立的词纳入了词典。
        在计算机里,可视信息是以一个大的比特阵列的形式存放的,每个比特对应一个微小的电子门,门可以打开,也可以关闭(事实上,半导体门的两个状态分别对应一个高电平和一个低电平,从软件的角度看,只有两个状态,通常称之为1态和0态)。图像上的每一个点对应计算机存储器内的一个或多个比特,以这种方式存储或显示的图像叫位图图像,或简单地称之为位图。通过改变计算机缓冲区各位的状态,可以控制显示的内容。显示硬件解释显示缓冲区的内容,从而在显示器屏幕上显示图像。

        屏幕的水平和垂直解析度对所显示的图像质量有很大的影响。下面的这张图片给出了在各种标准解析度下的同一幅图像,这四幅图片的解析度依次为:32位全彩色、16位真彩色、256色和16色。从理论上讲,分形中的带状卷须应连续下降到白色区域,在这个过程中将变得无限小。但实际上,由于解析度的限制,这些卷须消失了,最后变成了随机的灰色细毛的海洋,解析度越高,消失之前的卷须越细。
        视频硬件的颜色解析度对图像质量的影响也是非常大的(即使处理的不是全彩色图像,而是1、4、16或256个灰度的单色图像)。虽然是具有相同的水平和垂直解析度的各幅图像,但是,具有256个灰度等级的图像比黑白图像要真实的多。

        “256”究竟意味着什么?256种颜色有哪些?每种颜色又放在哪里?接下来的将介绍彩色图形编程的各种细节,但目前,还必须掌握基于调色板的显示方式的基本原理。
        当使用各种不同的显示模式时,软件把一个颜色编号放在相应于象素的计算机内存。在双色模式中,颜色编号只能取两个值:0或者1,通常0代表黑色,1代表白色(如果所用的显示器使用的是有颜色的荧光粉,则可能是淡黄色或绿色)。由于每个象素的颜色仅依赖于一个信息位,因此,这种颜色也叫“1比特”颜色。
        对于更复杂的颜色,要经过两步才能真正显示屏幕上每个象素的颜色。首先,软件把颜色编号放在相应于象素的计算机内存。在16色模式中,颜色的编号可以是0~15间的任一个值,由于存储16种不同的颜色需要4个信息位,所以16色模式叫“4比特”模式。同样,在256色模式中,每个象素颜色编号的取值可高达255,要存储象素的颜色需要8个信息位。
为了确定每个颜色编号所对应的真实颜色,显示硬件要参考调色板的颜色值。调色板是一组独立于存储各个象素颜色编号存储区的视频存储区。调色板中的颜色值指定了屏幕上象素的红、绿、蓝三个基色的混合比例,屏幕上的每个象素对应一个颜色号。不同的象素的颜色对应不同的调色板颜色值。
    存储调色板上每种颜色所需的准确位数取决于显示硬件,例如,在EGA调色板上的每种颜色值有6个比特,2比特用于红色,2比特用于绿色,2比特用于蓝色。
        颜色在经过图象处理软件的数字化处理之后,转变成了数字的形态,即由一个一个的位(Bit)所组成,位中存储颜色的情况如下:
1位 2种颜色
2位 4种颜色
4位 16种颜色
8位 256种颜色
16位 65536种颜色
24位 1677万种颜色
32位 1677万种颜色和256级灰度值
36位 687亿种颜色和4096级灰度值
        通常所称的标准VGA显示模式是8位显示模式,即在该模式下能显示256种颜色;而高彩色(HI COLOR)显示是16位显示模式,能显示65536种颜色,也称64K色;还有一种真彩色(TRUE COL?OR)显示模式是24位显示模式,能显示1677万种颜色,也称16M色,这是现在一般PC机所能达到的最高颜色显示模式,在该模式下看到的真彩色图象的色彩已和高清晰度照片没什么差别了。
     在图象文件的存储格式中也是以位来存储颜色的。由于图象文件的存储 格式非常多,这里仅以TRUEVISION公司设计的32位TGA文件格式为例简单说明,在该种格式文件中,32位被分为两部分,其中24位是颜色部分,另外8位是AL?PHA值部分,记录着256级灰度,用以加强真彩色的质量。

      计算机屏幕上的每一个象素对应内存中的一个数值,显示硬件解释该数值,以产生实际的色点。屏幕上象素的点数及颜色值决定了显示的解析度。屏幕上水平方向的象素个数叫水平解析度,每一列上象素的个数叫垂直解析度,给定时间内在屏幕上能够同时显示的颜色数叫颜色解析度。尽管从技术上来讲,解析度既指尺寸解析度又指颜色解析度,但通常所指的是水平和垂直方向的解析度(例如,虽然从技术上讲,颜色数是解析度的一部分,像“每一种视频适配器都有最大的解析度和最多的颜色数”这样是不准确的短语。
    从支持720×438的双色模色的大力神图形适配器,到支持1024×768的256色或更高模式的Super VGA卡,每一种视频适配器都有所支持的最大解析度及颜色数。大多数的图形硬件都支持几种不同的显示模式,从而能够为某一应用程序在速度、解析度和颜色数之间找到一种最佳的平衡。
随着图形硬件种类的不断增加,记住不同图形卡和不同模式下的解析度和颜色数并不是一件容易的事,各种不同的和PC兼容的图形卡所支持的显示模式是不尽相同的,好的显示卡会支持很高的分辨率。解析度高于VGA的卡通常划归于界限还不明确的Super VGA类(或简称为SVGA和SVG)。一些权威机构以及大多数的PC杂志,坚持把SVGA专用于800×600的模式,而用Super VGA、SVGA或“beyond SuperVGA”指1024×768或更高的解析度模式。
        由于Super VGA的范围很广,分类也不明确,很多用户难以找到支持自己特有的SVGA的软件,而程序员则更难写出支持大量SVGA卡的软件。幸运的是,在八十年代后期,成立了视频标准联合会,以设计急需的Super VGA标准。1989年,该显示硬件和图形软件联合会推出了主要基于800×600的标准,但许多工业界的领导人士提出批评,认为这一标准在出台之前就已过时。1990年,VESA推出了一个重全面的标准,以此作为回应,该标准包括了上至1280×1024的256色模式。 VESA标准包含一个编程SuperVGA的软件接口,通过一个特殊的驱动程序,现行卡制造商可以支持这种界面,而不需要改变其硬件结构。因此,可以找到支持几乎所有Super VGA卡的通用软件,不管这种软件是四年前的,还是新的,Super VGA解析度的VESA标准模式号都是一致的。
    请注意,目前几乎所有的Super VGA视频卡都能模仿传统的计算机图形适配器(Graphics——CGA)、增加图形适配器(Enhanced——EGA)和视频图形陈列(Video Graphics Array——VGA)的低解析度模式,某些Super VGA卡也模仿大力神图形适配器(Hercules Graphics Adapter,也叫做HGA或单色图形),低性能的大力神标准在非常便宜的PC中的应用也还可见。

    图形一般指用计算机绘制(draw)的画面,如直线、圆、圆弧、矩形、任意曲线和图表等;图像则指由输入设备捕捉实际场景画面产生的数字图像。数字图像通常有位图和矢量图形两种表示形式。
    位图图象 (bit-mapped-Graphics.Raster Graphics),以记录屏幕上图象的每一个黑白或彩色的象素来反映图象。每一个象素有特定的位置和颜色值。位图适用于具有复杂色彩、明度多变、虚实丰富的图象,例如照片、绘画等。使用位图格式的绘画程序叫做位图绘画程序,例如Adobe Photoshop 。 它以与屏幕相对应的存储位来记忆和处理图象,把图形作为点的集合,这是绘画程序应用的典型文件格式。位图图象依赖于解析度,放大和以高清晰度打印时,容易出现锯齿状的边缘。象素的多少决定文件的大小和图象细节的丰富程度。
位图图像由数字阵列信息组成,用以描述图像中各像素点的强度与颜色。位图适合于表现含有大量细节(如明暗变化、场景复杂和多种颜色等)的画面,并可直接、快速地在屏幕上显示出来。位圈占用存储空间较大。一般需要进行数据压缩。为了便于位图的存储和交流,产生了种类繁多的文件格式,常见有PCX、BMP、DLB、PIC、GIF、TGA和TIFF等。 

    矢量图形(Vector Graphics)的特点是,绘画程序中物体定位、形体构造建立在以数学方式记录构件(图形元素)的几何性质上,例如直线、曲线、圆形、方形的形状和大小。它不是记录象素的数量,在任何解析度下输出时都同样清晰。例如Adobe Illustrator就是使用这种格式的软件。矢量格式更适合于以线条物体定位为主的绘制,通常用于计算机辅助设计(CAD)和工艺美术设计、插图等。使用物体定位绘画程序可以把特定物体作为一组,单独改变线条的长度,放大或缩小原形,移动和重叠。但是在屏幕上显示的时候,由于监视器的特点,矢量图也是以象素方式来显示的。
    矢量图形是用一组指令集合来描述图形的内容,这些指令用来描述构成该图形的所有直线、圆、圆弧、矩形、曲线等图无的位置、维数和形状。在屏幕上显示矢量图形要有专门软件将描述图形的指令转换成在屏幕上显示的形状和颜色。用于产生和编辑矢量图形的程序通常称为Draw程序。这种程序可以产生和操作矢量图形的各个成分,并对矢量图形进行移动、缩放、旋转和扭曲等变换;使用矢量图形的一个很大的优点就是容易进行这类变换。但是,用矢量图形格式表示复杂图像(如人物或风景照片)的开销大大,因此矢量图形主要用于表示线框型的图画、工程制图、美术字等。绝大多大多数CAD和3D造型软件使用矢量图形作为基本的图形存储格式。
    矢量图的优点也就在于它在任何解析度下输出时都同样清晰。我们看下面这幅图片:左边的是矢量图文件,右边的是位图文件。虽然现在看起来好像位图文件的色彩更饱满一些。但经过放大后它就会显示出色点,而矢量图经过放大后,清晰度不会产生太大变化,这一点,我们在教程里作以了详细的介绍。

在前面的介绍中,我们分别提到过几种不同的分辨率, 初次进行数字图象的处理时,分辨率(Resolution)这个概念经常令人感到混乱。在这里谈一下图象处理中常见的也是最重要的几种分辨率类型:位分辨率、设备分辨率、网屏分辨率以及图象分辨率。
    1.屏幕分辨率
屏幕分辨率就是用户在屏幕上观察图象时,所感受到的分辨率。一般屏幕分辨率是由计算机的显示卡所决定的。例如标准的VGA显示卡的分辨率是640×480,即宽640点(象素),高480点(象素)。至于较高级的显示卡,通常可以支持800×600或是1024×768点以上。
    2. 位分辨率(Bit Resolution),又称位深,是用来衡量每个象素储存信息的位数。 这种分辨率决定了每次在屏幕上可显示多少种颜色。一般常见的有8位、24位或32位颜色。
    3. 设备分辨率(Device Resolution),又称输出分辨率,指的是各类输出设备每英寸上可产生的点数,如显示器、喷墨打印机、激光打印机、热式打印机、绘图仪分辨率。这种分辨率通过DPI(Dot Per Inch)这个单位来衡量。一般来讲,PC显示器的设备分辨率在60~120DPI之间,而打印机的设备分辨率则在180~720DPI之间,数值越高,效果越好。
    4.n网屏分辨率(Screen Resolution),又称网屏频率,指的是打印灰度级图象或分色所用的网屏上每英寸的点数。这种分辨率通过每英寸的行数(epi)来标定。
    5. 图象分辨率(Image Resolution), 指的是图象中储存的信息量,这种分辨率又有多种衡量法,典型的是以每英寸的象素数(ppi)来衡量。图象分辨率和图像尺寸一起决定文件的大小及输出质量。该值越大,图象文件所占用的磁盘空间也越大,进行打印或修改图象等操作所花时间也就越多。
图象分辨率以比例关系影响着文件的大小,即文件大小与其图象分辨率的平方成正比。如果保持图象尺寸不变,将其图象分辨率提高一倍,则其文件大小增大为原来的四倍。例如原图象的文件大小为841KB,图像分辨率为72ppi,保持图像尺寸不变,用图象处理软件提高其图象分辨率到144ppi,这时文件大小变为3364KB。
    图象分辨率也影响到图象在屏幕上的显示大小。如果在一台设备分辨率为72DPI的显示器上将图象分辨率从72ppi增大到144ppi(保持图象尺寸不变),那么该图象将以原图象实际尺寸的两倍显示在屏幕上。
    一般来说,降低图象分辨率后再增大是不明智的。由于降低图象分辨率时将删除图象中的一些原始信息,然后在增大其分辨率时又要重新计算丢失象素的色值以便增加信息,这时重新增大分辨率的图象就没有原来的高分辨率图象效果好了。
    6.打印机分辨率
打印机分辨率又称为输出分辨率,所指的是打印输出的分辨率极限,而打印机分辨率也决定了输出的质量。打印机分辨率越高,除了可以减少打印的锯齿边缘之外,在灰度的半色调表现上也会较为平滑。
打印机的分辨率通常是以dpi(每英寸中所包含的点数)来表示。目前市场上的打印机当中,24针的针式打印机的分辨率约为180dpi;而喷墨式打印机的分辨率可达300,甚至720dpi,不过如果真要打印这么高的分辨率,所使用的也必须是特殊的纸张;所以喷墨式打印机比较适合于个人作彩色输出使用。
除了喷墨打印机之外, 激光打印机的分辨率又要高一筹。较老的机型通常在300―360dpi之间,近来由于超微细碳粉技术的成熟,使得分辨率可以达到600甚至1200dpi,作为专业的排版输出这已经绰绰有余了。
    在专业输出上,也经常会使用到热升华彩色打印机作为输出设备,其分辨率的极限约为300点。
    7.扫描仪分辨率
扫描仪分辨率指的是扫描仪的解析极限,表示的方法和打印机分辨率相当类似,一般也以dpi来表示。不过正如前面所指出的那样,这里的点是指样点,与打印机的输出点是不同的。扫描仪的分辨率在纵向是由步进马达的精度来决定的,而横向则是由感光元件的密度来决定的。
    一般台式扫描仪的分辨率可以分为两种规格,第一种是光学分辨率,指的是扫描仪的硬件所真正扫描到的图象分辨率,目前市场上的产品级可以达到800-1200dpi以上。第二种则是输出分辨率,这是通过软件强化以及内插补点之后所产生的分辨率,大约为光学分辨率的3-4倍左右。所以当你见到一台分辨率号称2400dpi的扫描仪时,不要大惊小怪,先要看清楚这是光学分辨率还是输出分辨率。
    在扫描一幅数字图象之前所作的操作,将影响到最后图象文件的质量和使用性能。而其中很重要的一步就是确定扫描分辨率,它取决于图象将以何种方式显示或打印。
如果扫描图象用于640×480象素的屏幕显示,则扫描分辨率不必大于一般显示器屏幕的设备分辨率,即一般不超过120DPI。但在大多数情况下,扫描图象是为以后在高分辨率设备上输出而准备的,此时就需要采用较高的扫描分辨率。
如果图象扫描分辨率过低,图象处理软件可能会用单个象素的色值去创造一些半色调的点,这会导致输出的效果非常粗糙。反之,如果扫描分辨率过高,则数字图象中会产生超出打印所需要的信息。例如采用高于打印机网屏分辨率两倍的扫描分辨率产生的图象,在打印输出时就会使图象色调的细微过渡丢失,导致打印出的图象过于呆板无味。
    那么,应如何正确地设置扫描分辨率呢?一般情况下应使用打印输出的网屏分辨率、扫描和输出图象尺寸来计算正确的扫描分辨率。其步骤如下:
1)?用输出图象的最大尺寸乘以网屏分辨率,然后再乘以网线数比率 (通常情况下为2∶1) ,得到该图象所需象素总数。
2)?用象素总数除以扫描图象的最长尺寸即得到最优扫描分辨率。
用公式来描述即为:
图象扫描分辨率=输出图象最长尺寸×网屏分辨率×网线数比率/扫描图象最长尺寸
例如,扫描图象宽2英寸、高3英寸,需要打印机输出图象的宽为5英寸、高为6英寸,使用打印机的网屏分辨率为150epi,网线数比率为2∶1。
图象扫描分辨率=6×150×2/3=600DPI
上面我们介绍了这几种主要的分辨率,最后请读者在进行图形图象处理和应用程序设计时加以注意:虽然分辨率越高,所呈现出来的图象质量也越高,但这是要付出代价的,分辨率越高,则图象文件就会越大,所占的内存也会越多。

    人类信息交流中,最丰富的信息流是视觉媒体。凡是通过视觉传递信息的媒体,都属于视觉类媒体。它包括图形、图象、文字以及一切形象化的视觉信息形式。视觉类媒体特性研究,涉及光度学、色度学、图形学、数字信号处理和人类视觉胜利心理特性等,认识和运用其基本特征,是视觉媒体处理的各种技术之基础。作为一名专业的图形设计员来说,了解视觉媒体特性是必不可缺的。下面我们将从几个不同的侧面来分析和说明视觉类媒体的主要特性。
一、可见光谱与光度学参量
人眼所看到的客观存在的世界,通常称之为景象。客观物体所发出的光线或是物体受光源照射后所反射、透射的光,在人的视网膜上成象,是一种自然的生理功能,它使人能借助视媒体去认识世界。近代科学的发展,特别是光电转换技术进步,使人类能够以各种方法来记录、处理、传输客观景象,如各类图片、照片、绘画、文稿、X光胶片等;不仅是获取和记录那些人眼可见的图象信息还可利用非可见光和其它手段成象,或利用适当转换装置将其变为人眼可视图象,例如红外成象、超声成象、微波成象等;科学技术使人的视觉能力逐步增强和延伸。从物理上讲,光线是电磁波的一种能量辐射形式。电磁波的主要多数包括:传播方向,所具能量,极化情况和波长。电磁波的频率范围很宽,根据波长不同,具有不同性质,包括无线电波、红外线、可见光谱、紫外线、X射线、宇宙射线等。可见光谱在电磁波中仅是很窄的一段,其波长在380至780毫微米之间,波长不同呈现不同的颜色,从紫、蓝、绿、黄到橙、红,连续地变化描述方法使用如下物理量:光源发光强度、光通量、照度、亮度,还使用视敏曲线反映人眼的感觉特性。
二、三基色原理
不同波长的单色光会引起不同的彩色感觉,然而同样的彩色感觉却可以来源于不同的光谱成分的组合,这个事实说明,光谱分布与彩色感觉之间的关系是多对一的,也说明在彩色重现过程中并不要求客观景物反射光的光谱成分,而重要的是人眼应获得原景物的相同的彩色视觉。实验证实,大自然中几乎所有颜色都可以用几种基色按不同比例混合而得到。三基色原理包括如下内容:
1.选择三种相互独立的颜色,即不能以其中两种混合而得到第三种作为基色,将这三基色按不同比例进行组合,可获得自然界各种彩色感觉。如彩色电视技术中选用红(R)、绿(G)和蓝(B)作为基色,印染技术中选用黄、品红、青作为基色。
2.任意两种非基色的彩色相混合也可以得到一种新的彩色,但它应该等于把两种彩色各自分解为三基色,然后将基色分量分别相加后再相混合而得到的颜色。
3.三基色的大小决定彩色光的亮度,混合色的亮度等于各基色分量亮度之和。
4.三基色的比例决定混合色的色调,当三基色混合比例相同时,色调相同。
利用三基色原理,将彩色分解和重现,最终实现在视觉上的各种不同彩色,是彩色图象的显示和表达的基本方法。在各类彩色应用技术中,人们使用多种混色方法,但从本质上讲是两种: 相加混色和相减混色。
相减混色利用了滤光特性,即在白光中减去不需要的彩色,留下所需要颜色。如印染、颜料等采用的相减混色。相减混色关系式如:黄色= 白色-蓝色,青色= 白色-红色,红色= 白色-蓝色-绿色,黑色= 白色-蓝色-绿色-红色。
相加混色不仅运用三基色原理,还进一步利用人眼的视觉特性,产生较相减混色更宽的彩色范围。常用的相加混色方法有以下三种:
·时间混色法:将三基色按一定比例轮流投射到同一屏幕上,由于人眼的视觉惰性,只要交替速度足够快,产生的彩色视觉与三基色直接相混时一样。这是顺序制彩色电视图象显示的基础。
·空间混色法:将三基色同时投射到彼此距离很近的点上,利用人眼分辨力有限的特性而产生混色,或者使用空间坐标相同的三基色光的同时投射产生合成光,这是同时制彩色电视图象和计算机图象的显示基础。
·生理混色法:利用两只眼睛分别观看两个不同颜色的同一景象,也能获得混色效果。
三、视觉生理和心理规律
关于人眼视觉机理、人脑从景物提取信息等问题,科学家们还难以作出清楚的解释。视觉媒体的重要性促使人类对此进行大量的实验研究,并在此基础上提出假说,总结规律。运用视觉生理和心理过程的这些实验规律,在彩色电视实用化和图象工程应用上许多成功实践,表明这些视觉规律经得起考验,对研究发展视觉类媒体具有极其重要的意义。
人的眼睛是一个巧妙的器官,其视觉能力是令现代科学技术工作者惊叹的。现将主要规律概括如下:
1.视觉调节力
通过改变晶体的折射率,人眼可调节视距;依靠视细胞和瞳孔的调节,眼睛能适应非常宽的亮度范围,所能感受亮度上、下限之比为l000:1。控制眼球运动的肌肉有六种,使眼睛能自发的或反射性的,还有非自发的运动参与,扩宽视野或观察视媒体细节,具有更好的临场感受和恰当的扫描方式。
2.视觉暂留性
眼睛的另一个重要特性是视觉惰性,即光象一旦在视网膜上形成,视觉将会对这个光象的感觉维持一个有限的时间,这种生理现象叫做视觉暂留性。对于中等亮度的光刺激,视觉暂留时间约为0.05至0.2秒。视觉暂留性事实上是近代电影与电视的基础,因为运动的视频图象都是运用快速更换静态图象,利用视觉暂留性而在大脑中形成图象内容连续运动感觉的。光栅扫描技术、计算机动画设计也利用了视觉暂留性,精确安排视觉暂留时间。 3.视觉锐度
眼睛分辨景物细节的能力叫视力,又叫视觉锐度(Visual Acuteness)。眼睛分辨景物细节的能力有一极限值,若以人眼对被观察物体相邻最近两点的最小视角为θ来表示,则视力定义为该θ的倒数。θ以角分为单位,这与医学中视力定义一致。
视力与下述因素有关:人的视网膜上光敏细胞间物理距离决定人眼分辨率的极限,当景物成象在黄斑区,分辨率最高;当亮度和对比度过低,视力下降;当亮度过高,视力不会增加,甚至因“眩目”而降低。人限对彩色分辨率低于对亮度分辨率,而且对不同颜色构成的彩图细节的分辨率也不同。
4.亮度辨别力
人眼在比较两个强弱不同亮度时,有较好的判断力,对亮度变化过程敏感。对于不同亮度的背景,人能察觉到的最小亮度差别也不同。人眼分辨亮度的能力与背景亮度有关,也即对比灵敏度不同。对比度C定义为:C=Bmax/Bmix。Bmax和Bmix是重现图象或景物时的最大和最小亮度。只要保持该C常数,就可实现人眼亮度分辨的重现。 5.空间频率响应
在人眼视力范围之内,对于图象上不同空间频率成分具有不同的灵敏度。实验表明,人眼对中频响应较高,对高、低频的响应较低,因而视觉上会显出马赫带效应,即亮度突变处明显增强。这时人眼判读特定目标有利。
6.适应性及对比效应
人眼通过自身的适应性调节,摄取视觉空间的信息及其变化状态。具体适应性规律表现在以下几方面:
明暗条件变化下的眼适应,亮适应(即由暗到亮变化)时,几秒钟就能分辨出景象的明暗和颜色,其过程约在3分钟内达到稳定。暗适应(即由亮到暗处)时,几分钟才能分辨景象,约45分钟才稳定,过程要长些。

    大多数的输入设备都产生位图。主要的例外情况是定位设备,如数字化仪和鼠标,它们仅产生向量数据,常用位图输入设备有扫描仪(彩色或单色)、图象捕获板及传真机。
应用程序事实上决定了数据的文件格式,而不是输入设备的文件格式,例如Paint程序使用鼠标进行输入但仍然产生位图文件。扫描仪传输位图信息,但复杂的图形艺术程序可能将它转换成向量格式。大多数情况下文件格式的类型(位图或向量)要与输入设备相匹配。
    扫描仪是最常见的输入设备,现在扫描仪对有些格式都可用且功能很强,较典型的便宜的扫描仪是单色的,有2-―256级阴影,分辨率在每英寸60-400点之间(dpi)。有时选择低分辨率(dpi)的扫描仪,用软件来抖动图象,这样也能获得较多的灰度,许多单色扫描仪也有彩色偏差,例如红色不敏感或绿色不敏感,因此当扫描一彩色图象时,某些彩色就不能记录。
除了单色扫描之外,还有具备较高空间分辨率、较大彩色深度、有较多彩色的扫描仪。并且,对多达24位彩色的设备来讲本书中提到的大多数位图、打印机或图形元文件格式就足够了。说到底,空间分辨率和彩色深度的发展很快,如果还要加色彩学和光度学的数据,则最好使用TIFF或Postscript,它们可以具有彩色深度、分辨度、光度学和色彩学的数据。
    传真机是一个越来越常见的扫描和重建图形方法,一般说来,这种图象的数据由CCITT标准定义,包括数据压缩。像TIFF或PostScript这样的文件格式支持这些标准,因此很容易获取传真数据,对传真传输而言,许多传真机都模仿一般的打印机,因此也接受像Hewlett-packard的PCI文件格式。
    视频输入现在不太常见,但随着多媒体和其他应用的发展它会越来越常用,现在的多媒体应用中一般使用位图格式,如Trilevision的Targa,Amiga的IFF/LBM和CompuServe的GIF等。高速帧速率的多媒体应用特别要求效率和压缩比。它们要根据电视工业的标准进行画图。数据文件或数据流说明一直飞速发展,在众多格式中,只有JPEG能进入这个高性能的领域。
                                                    

 

    图形设备和系统

  


    大多数的输出设备都使用位图,而PostScript打印机(它使用向量数据和位图数据)及所有的绘图仪是例外。
    点阵打印(包括激光打印机)是最常用的输出设备。其本质是位图设备,但是,如果它们提供内嵌的解释器,如PostScript或HPGL,则可把打印机当成是向量设备。大多数打印机都有它本身的数据格式或工业标准格式(如PCT)。不包含解释器的打印机一般都能提供快速输出和较低的开销。彩色打印机包括简单的彩色色带点阵打印机、彩色喷墨打印机和彩色激光打印机。高性能的打印机不仅具有自己的格式而且还需要在应用中有定制的驱动程序或者支持彩色PostScript。
    绘图仪与点阵打印机类似,一般只接收符合厂家规定格式的数据。许多绘图仪已采用HPGL标准,要输出到绘图仪的应用(如CAD程序等)可以产生HPGL输出,还有一些可以阅读HPGL格式的文件。

    EGA卡是通过VLSI(超大规模集成电路)实现的,它拥有丰富的、灵活的图形功能,为图形应用的开发提供了有力支撑。
(1)显示模式。
表中列出了EGA卡的图形显示模式。表中可以看出EGA最多可提供16种彩色,最大分辨率为640×350。
模式6是CGA的最高分辨率模式,它支持的屏幕分辨率水平为640个象素,垂直为200个象素,但是只有两种彩色。
模式4和5是很普通的CGA图形模式,显示的分辨率是水平320象素,垂直为200象素,彩色有4种,每个象素为2位。
模式0dh的分辨率为320×200,彩色种类为16,每个象素用4位保存。
模式0eh不是CGA或MDA的兼容模式,但它提供了更多的彩色。
模式0fh是EGA卡独有的,它的分辨率为640×350象素。
模式10h所支持的分辨率与模式0fh一样,但它提供16种彩色。

    位映象图象可以是显示卡在图形模式下显示的任何图形画面。下面就以显示在屏幕上的图象为例进行讨论,在讨论时假走图象文件所保存的图象刚好可显示在显示器上。当一幅显示在EGA图形卡上的图象,在显示时是一幅彩色图象,打印后变成单色图象。
当然如果用户使用CGA图形卡来显示这幅图象,那么只能显示一部分,原因是CGA图形卡的分辨率只有640×200象素,而EGA图形卡的分辨率为640×350象素,当然,如果把这幅图象用VGA卡640×480分辨率显示,那么图象只能充满屏幕的上面部分,下面部分为空白。
    由于EGA卡的分辨率为640×350象素(模式0fh),一个屏幕总共有640×350=224,000个象素,显示器的屏幕数据保存在内存中,位于内存高区,图象数据的保存和程序或数据保存的方法类似,都是作为8位字节串。在单色图象中,每位对应于一个象素,这祥每个字节就包含8个象素,上面这幅图象所需的存储空间为224,000/8=28k字节。由于上面这种图像是用位映射到象素上的,所以也称这类图象为位映象图象(bitmapped image)。
    下面介绍把EGA卡上的图象保存到文件中的方法(这种文件显然就是我们所指的图文件)。EGA卡的屏幕数据保存在段A000H中,把该段中的28K字节拷贝到文件中即可,可用下面的程序段来完成这个任务:
FILE * fp;
if ( (fp = fopen (" SCREEN. BIN" , " wb”)) ! = NULL ) {kk1}
fwrite (MK_FP (0Xa200 , 0) , 1 , 28000 , fp ) ; 
fclose ( fp ) ;
} else printf("Error in creating file" ) ;
    上面这个程序片段很简单,由于只有28K字节的数据,程序执行速度也会很快,但是它的效率并不高,原因在于源图象中有大片空白,也就是说图象中有很多白色区域,没有必要把它们都放到图象文件中,当图象很大时,如果彩色再很多,那么图象文件必然要占据大量的存储区。

EGA卡是一种彩色能力及分辨率都属于中等的图形卡,在其中引人了调色板概念,使得可用的颜色总数比同时可显示的颜色数要多得多。当然彩色总数由每种颜色在调色板寄存器中的位数来决定。
VGA卡比EGA卡具有更强的彩色功能,同时显示的彩色可达256种。VGA卡与EGA卡类似,但EGA用的是数字显示接口,而VGA卡用的则是模拟显示接口。下面简单介绍VGA图形卡所
支持的显示模式。
上表是VGA图形卡所支持的图形显示模式,位于模式10h以前的模式与EGA卡的对应模式一样,这使得本来在EGA卡上运行的程序也可在VGA卡上运行。
模式11h支持的是VGA卡的最高分辨率,水平方向为640个象素,垂直方向为480个象素,但只支持2种彩色,这种模式下显存的开始地址为A000:0000。
模式12h也支持VGA卡的最高分辨率,但它具有16种彩色。对彩色图形应用程序而言,这是一种常用显示模式。
模式13h中,VGA卡提供256种颜色,这种模式是VGA卡的特有模式,分辨率为320×200象素,工作在这种模式下的VGA卡提供的颜色种类最多。

SuperVGA 产品在体系结构上和标准的VGA 卡有所不同,每一种SuperVGA卡的制造商都在其产品的体系结构上作了一些扩充,以适应新的显示模式,并且具有一些新的特点。
下面介绍SuperVGA所提供的增强型图形模式。
(1) 640×400,256色模式
这是多种适配器支持的逻辑分辨率,它要求有256KB显存。这种模式的实现方式通常和VGA的13h模式很类似,所不同的是每一扫描行的象素数和扫描行数比模式13h高一倍。
(2) 640×480,256色模式
这种模式要求VGA卡具有512K显存。
(3) 800×600,256色模式
这是用于大多数廉价的多频显示器的最高分辨率模式,它也要求相应的卡有512K显存。
(4) 1024×768,256色模式
这是目前的SuperVGA图形适配器上所见的较高分辨率。这种模式要求768K的显存。
(5) 800×600,16色模式
此种模式需240K显存,此种模式和模式12h的实现方式类似。
(6) 1024×768,16色模式
这是SuperVGA产品中共有的较高分辨率,只有稍好的显示器才支持这种分辨率。
上面介绍了多种增强的图形显示模式,每种SuperVGA产品所支持的图形显示模式不尽相同。

在EGA和VGA图形适配器中,可用两种方法表示彩色,一种是压缩象素法,另一种是彩色位平面法。在压缩象素法中,一个象素的所有彩色信息被压缩到字节中,如果字节中放不下,就要放到字中。
(a)压缩象素法
压缩象素法是在显示存储器中各个位平面各取一位,经过位运算后经显示卡显示在监视器上。
(b)彩色位平面法
在彩色位平面法中,把显示存储器分割成几个独立的存储位平面,每个位平面用于控制一种彩色成分,每一显示象素在每个位平面中占有1位位置。
根据用来复制彩色的介质的不同,彩色可以用两种基本方式中的一种进行表示:一种是增色系统,另一种是减色系统。
当彩色图画被印在纸上时,它们是用通常叫做CMY的彩色系统实现的。CMY中的C表示cyan(青蓝),M表示megenta(品红),Y则表示yellow(黄)。实际上,正确的叫法应是CMYK,K表示black(黑)。
减色合成法中的三原色是青蓝——一种适中的蓝色、品红——一种相当普遍的红颜色和纯黄色。用户也许曾在现实世界中见过这些颜色的样品。
激光打印机可用减色合成法打印彩色,它们的颜色就是用这些术语表示的。这种模型对视频显示器就不适用,因为视频显示器本身不是白的,它们是黑的,就是说在其沉寂状态它根本不发射任何光。显象管发出的任何光都被加到黑色上,这样,在计算机屏幕上生成颜色的方法和纸上所做的恰好相反,这叫做增色合成法,它的基色是红、绿、蓝。
要在计算机屏幕上显示彩色图形,必须采用某种机制使得每个象素能够决定在显象管的扫描束照亮它时需要多少百分比的红、绿和蓝光。
实现这一功能最易想到的办法是使每一个象素有三字节的相关信息,每一个字节对应一个基本增色的百分比。该方法有许多不足,其中一个典型的问题就是:一个具有EGA图形模式的屏幕大小的图象需要0.75MB以上的存储空间,该图象的磁盘文件即使是用基于复杂串的压缩技术也需要相当大的存储空间,这样的一个屏幕更新起来速度非常慢。
显然这种方法是不实用的。按这种方法构造的视频显示器可以一次同时用一千六百种不同彩色来产生图象,但是屏幕的更新速度是一必须着重考虑的因素,这个问题不解决,我们只好舍弃上述的显示器构造方法。

计算机技术发展到今天,以其不可抗拒的魅力获得了越来越广泛的应用,CAD/CAE应用程序、适用于互联网的3D应用程序、Internet广播技术、3D游戏以及能够提供卓越影音效果的DVD技术等这些3D图形密集型应用程序日渐成为计算机在各行业及家庭应用的主流,提供快速的三维图像生成速度。性能更高、视觉效果更逼真、更富娱乐性以及价格更加合理的个人电脑平台,形成当今用户迫切追求的目标。
这些主流技术的应用均以三维图像处理技术的应用作为核心。由于各专业用户及家庭用户对声、色、效的追求,使得作为图形处理核心的三维图像处理技术理所当然地成为推动未来一代可以与视窗兼容的图像处理平台发展的原动力,这一平台将为个人电脑用户提供前所未有的逼真的立休效果及卓越的运算性能。因此,要了解未来的计算机技术,就必须先了解三维图像处理的技术。
三维图像处理过程由创建三维模型及执行几何运算开始。一个完整的三维图像处理过程可分为物理运算、几何转换。剪切及光效、三角形设定和像素渲染四个阶段,其中需要进行大量的浮点运算(包括物理实体,几何转换、剪切、光效,以及三角形设定)和整数运算(包括三角形设定和像素渲染)。在图形渲染方面,图形加速卡厂商已获得了很大的进步, AGP图形加速卡的问世。更快的内存、更加适用于三维图像处理的应用接口(API)如Directx和OpenGL的出现,大大提高了处理复杂整数运算密集型过程的速度,但对于3D图像处理过程中大量浮点运算密集型过程的处理则需要由中央处理器完成。而目前的情况是,中央处理器的数据处理速度无法与图形加速卡的处理速度匹配,从而减慢了数据传输速度,因此在图像处理的前端阶段即形成了一个拖慢整体三维图像处理速度的瓶颈。如何解决这个瓶颈问题,成为目前各处理器生产厂商迫在眉睫的问题。

还是在1993年386大行其道的时候,对显示卡的要求是插上显示器能亮就成;到了1995的486时代,只要进得去WIN31,放得了VCD就足矣;这时偶尔听说过3D加速卡,也在心中不以为然他说:3D卡,那是什么东西?
1996年奔腾流行,3D游戏在市场上开始出现,但人们正对多媒体感兴趣,关注的是CPU,是好声卡,显示卡也就继续被冷落在一边,但3D卡的先头部队已经上市。
到了1997年上半年,随着几个3D游戏的发布及人们对游戏的“新认识”,加之各媒体的炒作和Virge芯片的热销,3D时代在某些人的心目中已经来临;下半年,E3大展开幕,其中最风光的莫过于3D游戏和3Dfx,一块好显卡的重要性在人们心中也提高到了前所未有的高度。此时,人们才愿意花大价钱配好显卡,而不象以前只用百把来块钱打发了事。
以后的芯片之争无疑会集中在3Dfx Voodoo2、Matrox G200、Intel i740 、 3DLabs Permedia 2、ATI RagePro Turbo和NVIDIA RIVA 128 and RIVA ZX这几款上。3Dfx Voodoo2我就不多罗唆了,大家想必已经从各种渠道知道了它的消息; Matrox G200则要讲几句:它是Matrox继Millennium II与Mystique 220之后推出的新东东,力图挽回Matrox现在的颓势;Intel i740是Intel进军3D芯片界的第一弹,也是Intel一个“大阴谋”的开始; 3DLabs Permedia 2则是3Dlabs公司继permedial后的新一代产品;ATI Rage Pro Turbo是ATI在原有的Rage Pro上的增强新一代;NVIDIARIVA 128 and RIVA ZX实际是同一种东西,多出的ZX意味着RIVA 128有了8MB的显存,可支持AGP×2模式了。每种芯片都有自己的优势,也都各有高招。比较显示芯片的具体性能从以下几个方面来做个比较:
1. 2D性能
在2D方面起主要作用的是芯片的RAMDAC与显示内存,这两项对显示的分辨率、颜色数与刷新率都有大影响。虽然说现在用户对2D性能的重视程度远不如以前,但2D性能的提升就如CPU速度的提升一样,与以前不可同日而语。现在的用户大部分时候都不必等着显示卡工作,而是显示卡等着用户来输入。所以单纯的测试软件所显示的数值已经不象以往那么能说明问题。
2. 3D性能
3D性能是检验现在显示芯片最重要的一个标准,也是本文讨论的重点。由于Vedoo系列只支持全屏3D加速,所以我们在这里说的3D性能其实是专指游戏中的3D性能
3. 3D质量
单纯地从速度上来确定一种芯片的好坏现在似乎没有说服力。现在的3D游戏不是光有速度感就玩得下去的,对画面的质量有些人似乎还看得更重要一些。
现在的36卡市场的火爆毋需多言。老一代的3D芯片由于性能同Voodoo相比差了不止几倍,早先被捧上天的S3 virge之流已经被划入了淘汰品的行列。Voodoo系列百分之七、八十的市场占有率现在是无人能出其右,但3Dfx也并不能高枕无忧。可以预见,下半年的3D市场一定会出现群雄混战的格局,好戏已经开始了。作为消费者的我们除了坐山观虎斗之外,剩下的就是捂着钱包偷笑了。

3DNow!TM技术的出现,为扫除计算机三维图像前端处理速度的瓶颈提供了一套理想的解决方案,为当今的x86处理器架构开创了截然不同的三维运算及多媒体运算能力。该技术与当前的x86软件兼容,并且无需为操作系统提供支持,同时所有专为3DNow!TM技术而设计的应用方案均可运行所有操作系统。因此,3DNow!技术所带来的是一项根本的技术性突破。从最近Ziff-Davis实验室的3D WinBench 98基准测试中不难找到来用3DNow! TM技术的AMD K6(r)-2处理器与Pentium II处理器的测试结果。在测试中,两者均采用相同的配置为基准,并采用微软即将推出的Directx 6.0。从测试结果来看, AMD K6(r)-2系统的三维性能显著胜出Pentium II系统,配置AMD K6(r)-2/300处理器的系统得分为1110分,而同等时钟频率的Peniium II 300系统只获得961分,即使是pentium II 400系统也仅仅得到ll20分的成绩。而采用Winstone(r)98测试的主流商用软件性能评分中,AMD K6(r)-2/ 300与Pentium II 300则不分伯仲,分获215和216分。
面对如此的高性能和相对低的价位,3DNow! TM技术一经推出即成为业界多家软件厂商注目的焦点,其中许多著名厂商的软件作品如Epic MegaGames的Unreal、Rage的Incoming、Microsoft的Baseball 3D、Imagine Studios的Ares Rising、Viewpoint DataLals的LiveArt 98等软件均已进行优化以便适用于3DNow!技术,这些软件现已出现在IT市场上。其他一些公司的相应优化软件也将于年内推出。预计在1998年年内,3DNow! TM技术掀起三维图像处理应用的狂潮已成定势。
3DNow! TM技术是AMD公司为消除传统的图像处理过程中进行浮点运算和多媒体应用程序的瓶颈而研究开发的一套创新的指令集,是该公司旨次为业界提供的三维图像处理创新技术,它带来了全新水平的三维图形性能及逼真的图形效果,开创了与三维图形加速卡同步运算的先河。该指令集共包含21个指令,可最大程度地支持称为“单指令多数据”(SIMD)的浮点运算。针对三维图像处理的早期阶段集中的大量浮点运算指令,该技术相应备有一组全新的单精度浮点指令,它们可加速物理及几何运算,舒缓瓶颈的问题,使中央处理器能够在速度上赶上图形加速卡,加快多媒体应用程序的运算过程,大幅提高三维图像运算性能及逼真度。由于采用3DNow!TM技术的AMD K6(r) -2处理器的微架构可以支持在图像处理过程中全面执行两条指令,即每一时钟周期执行高达两个3DNow! TM指令,同时每一指令又可执行两个单精度浮点运算,从而AMD K6(r)-2处理器在每一时钟周期内可执行四个浮点运算(包括加、减、乘)。此外,3DNow! TM技术还包括一个特定的SIMD整数指令,有关指令集可以执行SIMD整数运算。数据预取以及更快的MMX至浮点交换等功能,方便为象素动作提供补偿,从而提高了MPEG解码能力。AMD K6(r)-2/ 333的浮点性能最高可达1.333 Gigaflops,较pentium II 333及400的浮点性能优胜很多,后两者的浮点性能分别为0.333 Giganops和0.4 Gigaflops。即使是AMD K6(r)-2/300的最高浮点性能也可达到1.2 Gigaflops,比代表目前Intel公司最高性能的Pentium II 400的浮点性能还高三倍,这正是3DNow! TM技术带来的技术飞跃。

显示卡技术的关键是显示总线技术,到现在显示总线技术已经发展了ISA、VESA、LOCAL BUS、PCI和AGP技术4代。据统计,前三种结构的显示卡技术在97年升始走向没落,主要是用户对显示技术的要求随着3D显示技术和操作系统的技术突飞猛进的发展而提升。
在PCI技术雄霸桌面PC四年之后,INTEL推出了崭新的AGP显示技术,这种技术可以加速大多数的3D绘图、影像与2D绘图工作。AGP显示卡与AGP新式专用插槽,将PC绘图工作从繁忙的PCI总线中解放出来,搭建了一条高速直通公路。
AGP技术推出的直接受益者是3D加速能力的飞速提高,AGP可以让专门为其设计的3D游戏、软件与应用程序描绘出更加真实和快捷的效果,AGP可以解决目前从网络卡到音效卡都抢用PCI通道的拥挤状况。
AGP技术有两个核心内容:一是使用PC的系统主内存作为显示卡上显示内存的扩展延伸,这样可以大大增加显示内存的潜在容量;二是使用更高的专用总线频率66MHz、133MHz甚至266MHz,这样可以极大提高数据的传输率(AGP在66MHz时是266MB/s, PCI的33MHz只有133MB/s)。
目前流行的AGP主要有两种方式:
一是DMA方式,在这种方式下,显示卡不使用PC系统主内存做显示内存的扩展。这时显示卡只是利用高速的AGP总线来提高数据传输率。
二是DIME(Direct Memory Execute)方式,就是在显示卡上的显示内存的容量不够的时候,将系统主内存当作内存使用。将许多耗费显示内存的3D操作在系统内存内完成,这样在系统价格基本不变的前提下大大提高PC整机3D机能。未来的几个月内随着100MHz的高速SDRAM的普及和价格的回落,系统主内存的技术差距进一步缩小,这一切为DIMERR的使用创造一块良好的土壤。
个人电脑用户现在购买AGP显示卡时机已经成熟。PII技术的成熟和快速普及,AGP将可以取代PCI成为最普遍的显示绘图技术。
而利用APG技术的电脑整机并不会使新PC的售价提高多少,这样促进用户可以用有限的钱,选择市场上最好的产品组合成性能价格比最好的个人电脑。
随着INTEL 440BX这一类支持100MHz系统频率的主机板控制芯片在98年Q3的普及,使得系统总线的带宽进一步增大。100MHz系统频率的条件下AGP技术充分利用系统总线的优势可以完美发挥出来。
6月26日微软公司推出WINDOWS 98操作系统,在这种操作系统中完整支持AGP技术,使得在此操作系统的环境中显示效果AGP显示卡比PCI显示卡要好很多。许多WINDOWS 95的用户同样可以通过升级WINDOWS95的版本到OSR2.1,安装微软Directx5.0后一样可以完美体验到APG技术的好处。
针对APG增强技术设计的软件表现3D 的效果远比PCI显示卡的效果好。事实上,AGP 希望能淘汰PC 游戏里过于简单的3D 场景,执行相同的游戏是AGP 加速增强版本的某些视觉效果的改变令人印象深刻。在使用AGP 增强版本的G-police 测试的结果发现,原本PCI版本上的爆炸效果边缘出现的令人讨厌的锯齿不见了,而原本静态的广告变成绚丽的动态画面。

到现在为止,您应该已经掌握计算机图形图象处理的一些基本知识,在这里我们将以几个小的C语言程序段向您讲解一下如何用编制程序来进行图形调用和图象处理。
下面我们先来看一下这个利用VGA图形卡页面功能的程序:
include
main()
int graphdriver=DETECT,graphmode;
int i,j,k;
initgraph(&graphdriver,&graphmode,"");
cleardevice();
setactivepage(0);
rectangle(100,50,200,150);
setactivepage(1);
circle(150,750,50);
for(i=1;i<=20;i++)
{kk1}
setactivepage(0);
for(j=1;j<=30000;j++)
i=j;
setactivepage(1);
for(k=1;k<=30000;k++)
i=j;
}
getch();
closegraph();
}
接下来,我们以Turbo C语言来看一下如何实现对图形模式的控制:
Turbo C中提供了四个模式控制函数:
int far getgraphmode (void);
返回现行图形模式的值;
void far restorecrtmode(void);
恢复屏幕在图形初始化前的模式;
void far setgraphmode(int mode);
设置系统图形模式并清屏幕;
void far getmoderange(int graphdriver,int far *lomode,int far *hmode);
获得一个图形驱动程序可设置的图形模式的范围。
经过调用以上函数,可实现使显示器暂时离开图形模式
进入文本模式,进行文本输出,然后不经过initgraph再返回图形模式。
#include
#include
main()
{kk1}
int g_driver,g_mode,lo,hi;
g_driver=CGA;
//设置CGA的图形驱动程序 
getmodararge(CGA,&lo,&hi);
//获得CGA的图形模式范围
printf("CGA supporting modes range form %d to %d.\n",lo,hi);
//显示CGA显示模式范围
initgraph(&gdriver,&lo,"c://tc");
//初始化为CGA CO模式
bar3d(100,30,130,150,0,0);
//画一实心方块
getch();
g_mode=getgraphmode();
//获得现行图形模式
restorecrtmode();
//返回图形初始化前的显示模式
printf("Now is text mode, press any key back to graphics mode.\n");
getch();
setgraphmode (gmode);
//再回到图形模式
rectangle (50,30,100,130);
//画一长方形
getch ();
//等待敲一键
closegraph();
//关闭图形系统
}
上述程序是针对CGA显示器的,根据本章第一节中所列出的各种显示器及其显示模式的表格,可以很方便地将此程序修改为适用于其它显示器模式的程序。
                                                     

    图形文件的显示

  


    当白光通过棱镜折射时,它的色彩成份分离形成色彩排列,这是白光的光谱特点,并且色彩范围人眼都能看见的。形成光谱的色彩顺序是红色、橙色、黄色、绿色、蓝色、青色和紫色,按首字母缩写成ROYCBIV(最初把青色包括进去似乎为了使这个缩写能发音)。因为在光谱色彩中,基本色是红色、绿色和蓝色,所以光的色彩模式归结成RGB模式,和以前一样进行讨论。
    因为非白色光是有色的,会漏掉总光谱中的口部分,所以每种非白色光都折射自己本身的光谱。
尽管白色在CYM模型中是色素的缺乏(被画布白色代替)。但在RGB模型中黑色则是各种光的缺乏(被认为是真正的黑色)。三种基本光混合形戍白光。当三种基本光与其它每一种光混合时,形成淡青色、洋红色和黄色以及CMY颜色模式的原颜色。
    对于充分理解和掌握物质是如何在各种光的条件下显示的,光和颜色间的差别是一个重要概念,它们是对立的,然而又互辅相成。一个主模式补充其分模式。RGB发射光,而CYM反射光。如果没有光的照射,就不能看见物体的颜色,而有色光必须照在不透明的表面上才能看见。混合所有色彩光形成白色,而混合所有色素色彩形成黑色。最后,RGB通过增加进行混合,而CYM通过减少进行混合。
    光的三个重迭聚光点说明了这个基本模式。这里,色彩中没有黑 是由红、绿、蓝三种原色产主的。由于光的混合,形成了淡青色,洋红色和黄色。同时观察这两种模式,可见,RGB模式是CMY模式的对立物,就像每一种主体部分都是另一种的从属部分。 
    利用红(Red)、蓝(Blue)和绿(Green)种基本颜色,可以配制出绝大部分肉眼能看到的颜色。像彩色电视机的显像管(CRT)以及计算机屏幕,都是以这种方式来混合出各种不同的颜色效果。 RGB模式的混色原理是以颜色加法来混合出各种不同的颜色。

    白色光也是由三中原色:红、绿、蓝混合而成的。光谱中原色互补色是青蓝色、品红色和黄色。所以用这三种原色构成所有颜色。这三种原色构成了CYM色彩模式。在CYM色彩模式中,红色是品红色与黄色混合而成,蓝色是青蓝色与品红色混合而成的,而大多数人所认为的黄色总是带点品红色。CYM色彩模式步流行的原因之一是这些深色的原色是不自然的,而且不容易得到。自然界中真正的原色是很少的。
在理论上,利用C、M、Y三种基本色便可以混合为黑色,但是由于考虑到印刷油墨混合的误差,所以有必要再加进一定量的黑色。但使用CYM的一个重要的难度是原色混在一起会产生黑色,而不是棕色。这就是为什么画家可以通过混合RGB中强烈的相邻色彩得到接近黑色的原因。 
    CMYK是由青(Cyan)、品红(Magenta)、黄(Yellow)以及黑(Black)四种基本色调配合成各种不同的颜色,一般应用在印刷输出的分色处理上面。
    CMYK与RGB所不同的地方,除了组成的基本色不同之外,RGB的合成是采取颜色加法,而CMYK则是采用颜色减法。

    Lab模式是一般人较为陌生的色彩模式,这个模式的色彩定义石油国际照明委员会CIE所制定的,也是目前所有模式中含钙色彩范围最广的模式。它的特色是对色彩的描述完全采用数学方式,与系统及设备无关,因此它可以无偏差地在系统与平台间进行转换。 
    Lab模式是以一个亮度分量L(Lightness)——范围是 0-100;以及两个颜色分量a与b来表示颜色。a分量是由绿色演变到红色——范围是 -120-120;而b分量则是由蓝色演变到黄色——范围是 -120-120。
    人眼所能看见的光、色之范围较为广泛,由计算机的彩色屏幕按RGB模式或由彩色印刷品按CMYK模式所表示出来的光与色,只不过是其中的一部分。而且像RGB、CMYK和Lab三种模式表现的颜色范围也不相同。

    HSB模式是利用色相(Hue)、色浓度(Saturation)以及亮度(Brightness)三种基本向量来表示一种颜色。
* Hue:色调,沿着色调环从0度(纯红)转变为其他颜色,再转回360度(纯红)。
    * Saturation:色彩的饱和度。0 %时为灰色,100 %时为纯色。
    * Brightness:亮度,0 %为黑色,100 %时为白色。
所谓的色相,指的是不同波长的光谱,例如红色和绿色便是属于不同的色相。
    色浓度则是指颜色的深浅,例如同样是红色,也会因为浓度的不同而分为深红或浅红。至于亮度则指的是颜色明暗的程度。

在灰度图象中,每一个
象素都以8位元表示,因此
可以表现出256种层次。如
果将纯黑和纯白之间的层次
等分成256个层次,就成了
256灰度模式,可以用来模
拟黑白照片的图像效果。
所以每一个位元都是介
于黑色与白色之间256种灰
度的一种。灰度图象中只有灰度而没有彩色,所以除了与彩色有关的命令之外,几乎所有的功能都可以支持灰度图象。虽然黑白照片中的层次是连续的,同时层次远远超过了256个,但就一般应用美术的要求而言,256个层次已足以将黑白图像表现得相当完美了。

黑白图象是1位元的图象,也就是每个象素都由一个位元来表示,不是黑色就是白色。
它所占用的内存最小,但所支持的功能也最受限制。例如黑白图象并不能够表现出渐变的色彩。当图象从灰度转换黑白时,用户可以在对话框中选择转换成为什么样子黑白图象。
黑白模式无法表现层次复杂的图像,但可以制作黑白的线条图(Line Art),或是特殊的二层次高反差图像。
注意,其他的彩色图象并不能直接转换成为黑白图象,必须先将它转换成为灰度图象,然后才能将其转换成为黑白图象。

在RGB或是CMYK等彩色图象中,每一个象素都可以表现出完整的颜色信息,不过所占用的内存也相当大。而经过统计,一张RGB图象当中所真正使用的色彩,往往多只有几百种甚至只有几十种。所以聪明的工程师就想到,如果以这些所使用到的颜色建立一张颜色表(Color Table),而每一个象素当中的数值则表示为这个颜色表中的某个位置,那么岂不就可以节约相当多的内存吗?举例来说,一个24位RGB的图象,转变成为256色(8位每象素)的索引图象之后,所占用的内存容量只有原来的三分之一,这就是所谓的索引色的由来。
不过将图象转换成为索引色模式之后,所必须要注意的有两件事,第一是索引色图象所支持的功能比起RGB、CMYK图象来要少得多,因为索引色图象并不包含连续的色调变化所以许多的滤镜和渐变功能,多半不支持索引色图象。其次如果索引色所能表现的颜色数目,远少于原来图象中的颜色种类时,便会在原本平滑的图象表面,造成边缘效应,从以下的图象当中可以发现,随着位数的减少,边缘效应也愈加明显。所以除非特殊需要或是万不得已,否则我们还是用RGB、Lab或是CMYK模式来处理图象。

一般工业上的标准是以CMYK四种油墨来印刷彩色出版物。但我们身边的许多印刷物,例如名片,往往只需要用到两种油墨的颜色就可以表现出彩色效果。如果并不需要全彩色的印刷质量,这时我们便可以考虑利用双色印刷来节省成本。
另一方面,虽然我们在图象当中每一种油墨都可以指定256种不同的灰度阶层,但是在实际的印刷机上,机器所能分辨的压力强弱却不超过50种。所以当我们要印出灰度要求较细密的图象时,也可以利用双色印刷,指定其中一种油墨为黑色,另一种则为较淡的灰色,以此印出灰度层次较密的出版物。
除Duotone模式之外,用户也可以选择Monotone(单色)、Tritone(三色)以及Quadtone(四色)等不同的方式,以产生特殊的印刷设计效果。

在彩色显示设备所能显示的彩色数目有限的情况下,要显示由多种彩色的图像就需要使用其他技术,其中常用的一种就是彩色抖动。彩色抖动的工作原理与黑白抖动的工作原理相同。它们的基本思想是通过牺牲空间分辨率来换取灰度或彩色分辨率。假设用4个象素表示一个大象素,那么如果原来只有两种彩色,现在就有多种彩色。当然这里的多种彩色是一种“错觉”,因为其他彩色是两种原色的象素按一定规则排列而得到。

行程长度压缩
原理是将一扫描行中的颜色值相同的相邻像素用一个计数值和那些像素的颜色值来代替。例如:aaabccccccddeee,则可用3a1b6c2d3e来代替。对于拥有大面积,相同颜色区域的图像,用RLE压缩方法非常有效。由RLE原理派生出许多具体行程压缩方法:
1.PCX行程压缩方法:
该算法实际上是位映射格式到压缩格式的转换算法,该算法对于连续出现1次的字节Ch,若Ch>0xc0则压缩时在该字节前加上0xc1,否则直接输出Ch,对于连续出现N次的字节Ch,则压缩成0xc0+N,Ch这两个字节,因而N最大只能为ff-c0=3fh(十进制为63),当N大于63时,则需分多次压缩。
2.BI_RLE8压缩方法:
在WINDOWS 3.0、3.1的位图文件中采用了这种压缩方法。该压缩方法编码也是以两个字节为基本单位。其中第一个字节规定了用第二个字节指定的颜色重复次数。如编码0504表示从当前位置开始连续显示5个颜色值为04的像素。当第二个字节为零时第二个字节有特殊含义:0表示行末;
1表示图末;2转义后面2个字节,这两个字节分别表示一像素相对于当前位置的水平位移和垂直位移。这种压缩方法所能压缩的图像像素位数最大为8位(256色)图像。
3.BI_RLE压缩方法:
该方法也用于WINDOWS 3.0/3.1位图文件中,它与BI_RLE8编码类似,唯一不同是:BI_RLE4的一个字节包含了两个像素的颜色,因此,它只能压缩的颜色数不超过16的图像。因而这种压缩应用范围有限。
4.紧缩位压缩方法(Packbits):
该方法是用于Apple公司的Macintosh机上的位图数据压缩方法,TIFF规范中使用了这种方法,这种压缩方法与BI_RLE8压缩方法相似,如1c1c1c1c2132325648压缩为:831c2181325648,显而易见,这种压缩方法最好情况是每连续128个字节相同,这128个字节可压缩为一个数值7f。这种方法还是非常有效的。

霍夫曼编码压缩也是一种常用的压缩方法。是1952年为文本文件建立的,其基本原理是频繁使用的数据用较短的代码代替,很少使用的数据用较长的代码代替,每个数据的代码各不相同。这些代码都是二进制码,且码的长度是可变的。如:有一个原始数据序列, ABACCDAA则编码为A(0),B(10),C(110),D111),压缩后为010011011011100 。 产生霍夫曼编码需要对原始数据扫描两遍,第一遍扫描要精确地统计出原始数据中的每个值出现的频率,第二遍是建立霍夫曼树并进行编码,由于需要建立二叉树并遍历二叉树生成编码,因此数据压缩和还原速度都较慢,但简单有效,因而得到广泛的应用。

LZW压缩技术比其它大多数压缩技术都复杂,压缩效率也较高。其基本原理是把每一个第一次出现的字符串用一个数值来编码,在还原程序中再将这个数值还成原来的字符串,如用数值0x100代替字符串"abccddeee"这样每当出现该字符串时,都用0x100代替,起到了压缩的作用。至于0x100与字符串的对应关系则是在压缩过程中动态生成的,而且这种对应关系是隐含在压缩数据中,随着解压缩的进行这张编码表会从压缩数据中逐步得到恢复,后面的压缩数据再根据前面数据产生的对应关系产生更多的对应关系。直到压缩文件结束为止。LZW是可逆的,所有信息全部保留。
算术压缩方法
算术压缩与霍夫曼编码压缩方法类似,只不过它比霍夫曼编码更加有效。算术压缩适合于由相同的重复序列组成的文件,算术压缩接近压缩的理论极限。这种方法,是将不同的序列映像到0到1之间的区域内,该区域表示成可变精度(位数)的二进制小数,越不常见的数据要的精度越高(更多的位数),这种方法比较复杂,因而不太常用。
JPEG(JointPhotographicExprertsGroup联合摄影专家组)JPEG标准与其它的标准不同,它定义了不兼容的编码方法,在它最常用的模式中,它是带失真的,一个从JPEG文件恢复出来的图像与原始图像总是不同的,但有损压缩重建后的图像常常比原始图像的效果更好。JPEG的另一个显著的特点是它的压缩比例相当高,原图像大小与压缩后的图像大小相比,比例可以从1 %到80~90 %不等。这种方法效果也好,适合多媒体系统。
这里我们来看一下这个压缩Gif文件的LZW算法:
/* LZW.C
void PackGif(FILE *fp,char far *ImageData,
unsigned int width,unsigned int depth,int BitsPerPixel)
{kk1}
register int i,j;
unsigned char ThisChar;
int code;
char far *bitmap = ImageData;

index = 1;
pixmask = 0xff;
remcnt = 0;
rem = 0;
InitCnt = 0;
First_Char = BitsPerPixel;
CLEAR = 1 << BitsPerPixel; . //256,16,or 4
EOI = CLEAR + 1;
oldcode = -1;
memset( CTfirst,0,4096 );
memset( CTlink,0,8192 );
memset( CTnext,0,8192 );

PackInit( CLEAR );
PutCode( fp,CLEAR );

for( i=0;i{kk1}
for( j=0;j{kk1}
ThisChar = bitmap[j];
code = LookupCT(oldcode,ThisChar);
if( code != -1)
oldcode = code;
else{kk1}
PutCode( fp,oldcode );
oldcode = (int) ThisChar;
}
if (nextcode>nextlim)
{kk1} if (reqcnt==12)
{kk1}
PutCode( fp,oldcode );
PutCode( fp,CLEAR ); /* Position */
PackInit(CLEAR); }
else {kk1} reqcnt ++;
nextlim = nextlim << 1;
if (reqcnt==12) nextlim--;
} } }
bitmap = farptr( bitmap,(long)width ); }
PutCode( fp,oldcode );
PutCode( fp,EOI );
Flush( fp );
fputc( 0,fp );
fputc( 0x3b,fp );
return ;
}
                                                     

    图形文件的格式

  


    近年来,个人计算机和工作站上的图形工具比几年前的巨型机上的图形工具还要多,计算机图形学的领域也随之扩展。过去,当人们编出越来越多的图形应用程序后,需要把图像文件存储下来以作日后的处理或显示之用。在缺乏广为接受的标准的情况下,每个应用程序开发者都提出文件格式以支持其应用程序。从八十年代初以后,官方的边准组织开始提出首批通用的图形子程序和图形文件,以促成文件代码在不同的应用程序和硬件上的可移植性。
随之而来的结果试图性格是数量的急剧膨胀,在数以百计的应用程序中所使用的格式由几十种之多。例如在著名的图形处理软件——PhotoShop 5.0中用到的图像文件格式、子格式就共有三十多种。
简单举几种格式来说:PCX、MacPaint、Tiff、Gif、GEM、IFF/ILBM、Targa、BMP/DIB、WPG、PostScript、Sun、PBM、XBM、JPEG、FITS、DXF、HP-GL、LotusPic、PCL、WMF、EPS、CGM、RIB、FLI/FLC、MPEG、PDF……。对于如此繁杂的文件格式,我们每不可能接触或掌握全部的格式。在本章中,我们对最常用的文件格式进行分类归档,对读者作以详细的介绍。下面让我们先来看一下最常见的文件格式——BMP文件。

1.优点 在Microsoft Windows下得到广泛使用,支持稀疏的位元映射。
2.缺点 除了Microsoft Windows外,无法在其他环境下使用。
3.变体 Windows 3.0还可以从OS/2 Presentation Manager l.x中读取BMP文件。
4.综述 BMP格式又称为DIB,也就是Microsoft Windows设备无关位元映射(Microsoft Device Independent Bitmap)文件,Bmp可以包含每个象点l位元、4位元、8位元或24位元的图形。其中1、4和8位元图形有彩色映像,而24位元图形则是全彩(TrueColor)。

一、文件头
所有的DIB文件含有一个共同的文件头。
bfOffBits字段含有从文件头的最后(字节14)到图像数据位开始之间的字节数,这样就能方便地跳过位图头。
文件位图格式的两种变体可以通过查看位图头的第一个字(文件偏移字节14)来区分如果该字为12,则是一个os/2 格式文件,如果为40,则是Windows 3.x格式文件。
二、Windows的位图头
文件图后面为位图头和可选的彩色图像。位图头的结构有时称作BITMAPINFO,而带有色彩对应表的则为BITMAPINFOHEADER。
三、色彩对应表
使用每个像素1、4或8位的图像必然有一个色彩对应表。彩色映像的大小一般为2、16或256个表项,但如果图像不需要一个色彩全集,则表项可以更少些。如果biClrUsed字段为非零,则它包含使用的色彩数目,同时它也是色彩对应表的表项数目。如果字段为0,则色彩对应表为全部大小。24位图像没有色彩对应表,这种图像是直接RGB色彩。biClrUsed字段可以是非零,以提供一个建议的色彩大小。
由于显示设备可能不具备图像所需的那么多色彩,因此色彩对应表中的表项应该让最重要的色彩排列在先。 biClrImportant字段如果不为零,则指出了对于重新生成好的图像而言,有多少种颜色是重要的。
色彩对应表每个表项都有四个字节。
四、Windows的位图数据
位图数据紧跟在色彩对应表的后面。数据可以是不压缩的,如果要压缩,则4位和8位的图像可以使用一种RLE压缩方法。
位从逻辑角度看是每次存入一行(没有压缩时从物理角度看也是如此),每行被填充到一个四字节边界。每个像素一位的位图
每个像素只有一位,每字节有8个像素。字节中的最高位对应于最左边的像素。每个像素四位的位图,没有压缩的图像是每个字节有两个像素,高四位为最左边的像素,且每行填充到一个四字节边界。
压缩过的图像使用一种RLE编码格式,由一系列组组成。有三种类型的组:重复组、文字组和特殊组。
重复组由两个字节组成。第一个字节是像素计数值,第两个字节是一对象素,该组用第一个字节表示像素数,第两个字节为2个像素。例如,十六进制字节:05 24表示像素2 4 2 4 2
文字由一个零字节、一个像素值字节和文字像素字节组成。像素计数值必须至少为3(只有一个或两个像素时可以用重复组编码)。文字像素用0填充到一个偶数值。
例如,十六进制字节:
0005 1234 5000(注意填充到偶数字节)
表示像素 1 2 3 4 5
特殊序列00 00表示一行的结束,特殊序列00 01表示位图的结束,特殊序列00 02 xx yy是一个位置增量,说明把图像向右走xx个像素和向下走yy个像素。每个像素8位的位图
没有压缩的图像为一个字节一个像素,每行填充到四字节边界。压缩过的图像使用RLE编码格式,后者由一系列的组组成。组有三种类型:重复组、文字组和特殊组。重复组由两个字节组成,第一个字节为像素计数值,第两个字节为像素值。例如,十六进制字节:05 24表示像素 24 24 24 24 24。文字组由一个零字节、一个像素计数值字节和文字像素字节组成。像素计数值必须至少是3。文字像素用0填充到偶数字节数。
例如,十六进制字节:
0005 1234 5678 9A00(注意填充到偶数字节)表示像素 12 34 56 78 9A特殊序列与四位的位图一样。每个像素二十四位的位图。每个像素为3个字节,顺序依次为红、绿和蓝的值。每行用0填充到四字节的边界。
以上我们介绍了bmp文件的格式,下面我们看一下如何用C++语言来实现bmp文件的读取,下面是例程bmpread中的DisplayBitmap函数:
BOOL CMainWindow::DisplayBitmap(HDC hDC, HBITMAP hBitmap,
int X, int Y, DWORD RopCode)
{kk1}
// Create compatible display context
HDC hCompatDC = CreateCompatibleDC(hDC);

// Select bitmap into compatible display context
HBITMAP hOldBitmap = SelectBitmap(hCompatDC, hBitmap);


// Get dimensions of bitmap
BITMAP BM;
GetObject(hBitmap, sizeof(BM), &BM);

// Blast those bits to the screen
BOOL result;
result = BitBlt(hDC, X, Y, BM.bmWidth, BM.bmHeight,
hCompatDC, 0, 0, RopCode);

// De-select the bitmap
SelectBitmap(hCompatDC, hOldBitmap);

// Clean up after we are done
DeleteDC(hCompatDC);

return result;
}


1、优点 提供足够的信息并很好地组织这些信息,使得许多不同的输出设备能够方便地交换图形,由于CompuServe网络的广泛流行,许多平台都支持GIF。CompuServe通过免费发行格式说明书来推广自己。GIF支持24位元彩色,由一个最多有256种颜色的调色板实现,图形大小最多是64K×64K个象点。GIF的特点,包括LZW压缩、多图形的定序、交错屏幕绘图以及文字重叠。
2、缺点 现行的GIF版本不能多于256个24位元彩色,它没有为储存灰度或彩色校正数据作准备,也不能储存CMYK或HSI格式的数据。
3、变体 GIF没有各种明显不同的模式(不像TIF或PCX那样)。尽管如此, GIF仍可以有许多的变化,一些变体和选择包括:
·多图形
·每个象点的元数
·“逻辑”屏幕(围绕所有后续图形的一个图形平面)大小(用象点计算)
·逻辑屏幕的横宽尺寸比
·逻辑屏幕中图形的大小和位置
·重叠文字的存在、大小和位置
·图形序列—延时、使用者提示、交错的传输或者前一个图形的恢复。
在早期GIF规范和当前规范之间存在一些变体(有两个GIF规范版本,一个在1987年,为“87a”,还有一个在1989年,为“89a”),新设计的应该使用最新版本,这里介绍的是最新版本。
4、综述 它主要是为数据序列设计的一种传输格式,而不是作为文件的储存格式,换句话说,它具有顺序的组织形式(像TIF那样的储存格式,更普遍地使用随机组织形式,而不是顺序组织形)。这种顺序性质对图形没有什么实际影响,除了多个图形的顺序传输和显示这种专门的和特殊的情况。

了理解GIF,请记住它主要是为数据流而设计的一种传输格式,而不是作为文件的存储格式。换句话说,它具有顺序的组织形式(像TIFF那样的存储格式,则更普遍地使用随机组织形式,而不是顺序组织形式)。
GIF有五个主要部分以固定顺序出现,所有部分均由一个或多个块(block)组成。每个块由第一个字节中的标识码或特征码标识。这些部分的颀序为:头块、逻辑屏幕描述块、可选的“全局”色彩表块(调色板)、各图像数据块(或专用的块)以及尾块(结束码)。
下面是这些部分的内容:
(1)头是一个块,它识别数据流为GIF,并指示恰当地解释后面的数据所需的最早版本的GIF解码程序(87a或89a)。
(2)逻辑程序描述块定义了包围所有后面图像的一个图像平面的大小、纵横尺寸比以及色彩深度(它类似于产生图像的监视器屏幕)。它还指明后面跟随的是否为“全局”色彩表。
(3)全局色彩表(如果存在)构成一个24位RGB元组的调色板(每种底色为一个字节)。如果后面的像没有其自己的“局部”调色板,那么全局色表就是缺省调色板。
(4)后续数据作为“图形”或“专用”块出现。图形块典型地包含一个或多个位图图像,也可能是覆盖的文本。专用块或者包含一个专用应用程序码,或者包含一句不可打印的注释。
(5)最后的尾块只是值为3B(十六进制)的一个字节,表示数据流已结束。
注意:文件中的GIF数据流可能根本就不包含任何位图数据,这时,它只是要传输全局色彩表,作为没有自己调色板的后续数据流的缺省调色板。
这里是一个显示Gif文件的C语言程序,下面的代码是例程gifshow中的主函数部分:
main(argc,argv)
int argc;
char *argv[];
{kk1}
int i;

if (argc<=1) {kk1}
Sound();
graphinit();
ShowPIC( (char far *)bufptr, Width, Depth);

if (argc>=3) strcpy(filename, argv[2]);
else {kk1}
strcpy(filename+2, argv[1]);
strncpy(filename, "X_", 2);
if ((strchr(filename,'.')-filename)>8) strcpy(filename+8, ".GIF");
}
for (i=0; i<(1<palette[i] <<= 2;
packgif(filename, Width, Depth, BitsPerPixel, pgh->palette, bufptr);
Sound();

free(buffer);
SetVGAMode(0x03);
exit(0);
return 1;
}

1.优点 PCX是最老的,因此也是个人电脑软件中得到最为广泛使用的位元映射格式之一。当前的版本可使用24位元彩色,现实最多256色的调色扳或者全24位元的RGB,图形大小最多达64K×64K象点。数据是以运行长度编码(Run-Length Encoding)压缩。
2.缺点 文件格式没有为储存灰度或彩色校正表留有余地,即不能储存CMYK (代表青色Cyaneous、紫红Magenta和黄色Yellow三种基本色,加上黑色black作对比)格式数据,也不能储存HSI格式数据[虽然有些Zsoft程序允许使用HSI (代表色调Hue、饱和度Saturetion和亮度Intensity)来调整彩色值]。它的运行长度压缩方法效率不高,尤其是对于扫描图形或视频信息图形。由于PCX的发展年代较老, PCX文件可以使用各种调色板技术,但其结果是大多数阅读程序不能处理所有可能的PCX格式图形。
3.变体 PCX随着Zsoft产品的新版本而升级,在文件头(Header)中的一个序码确定了该文件所能使用的Zsoft产品的版本。其中版本0为基本单色(2色)或4色图形;版本2在版本0的基础上加上了16色图形,版本5又加上了24位调色板的256色和全24位RGB彩色。

一、概述
PCX格式由三个部分组成,即文件头、位图数据(较新版本的)和一个可达256种色彩的调色板。
其文件由固定128字节的文件头开始。它除了版本号以外,还包括被打印或扫描图像的分辨率(单位为每英寸点数)、大小(单位为像素数)、每扫描行字节数、每像素位数和彩色平面数。文件还可能包括一个调色板以及表明该调色板是灰度还是彩色的一个代码。
文件的核心部分是位图数据。位图数据以类似于Packbits 压缩法的运行长度压缩形式记录,像素值通常是单字节的指针,指向调色板中的位置。
如果版本号为5,则文件末尾处还有一个单一的位平面,一个RGB值的256色调色板三种底色各一个字节)。
二、详解
PCX 格式用于写是相对较简单的,但用于读就比较棘手,除非知道被解码图像的很多细节内容(如位深度和调色板等)。因此,以下的阐述都是基于最坏的情况,即读取一个其特性和年代都未确定的PCX文件,所有的数均是little-endian(Intel)格式,即LSB在先。
字节0,Zsoft标志
总是十进制值160,即十六进制A0。
字节1,版本号,
一定程度上不可靠的文件内容指南,见前面一节“变体”的讨论。
字节2,编码
到目前为止,总是为1。当前编码(压缩)方法只有一种,即在下面“位图数据),一节中讲述的运行长度法。
字节3,每像素位数
实际上是每个位平面的每像素位数,可能的值是1、2、4或8。
字节4-11,图像大小
图像大小由最小的和最大的极限给出。通常的下限是0。所有的极限均用16位无符号整数表示,单位为像素。图像大小可以这样计算:XSIZE=Xmax―Xmin+l; YSIZE=Ymax-Ymin+l,单位为像素。
字节12-15,以每英寸点数为单位的水平和垂直分辨率
这两个16位的数字有点古怪,它们对于定义所存储图像不起任何作用,但是,当它们与图像大小组合起来加以考虑时,能产生出被扫描图像的原始大小,或者被打印图像的希望大小,以英寸为单位。
字节16-63,头调色板
这一字段看上去只适用于带有单一位平面、16种或更少的颜色以及版本号为2的文件(参见下面“解释数据的关键”口节)。使用时,调色板拥有16组三元组的单字节调色板值。
字节65,色彩平面
PCX图像可以是单色彩,也可以是多个色彩平面的(参见第一章)。头的这个字节给出色彩平面数,它是正确翻译PCX文件的关键。
字节66,每行字节数
实际是每个平面的每行的字节数—存储末压缩图像一个扫描行的一个色彩平面所需要内存字节数,它总是偶数。
字节68,头调色板翻译
1=彩色/单色;2=灰度。Paintbrush IV或Paintbrush IV PIus中不使用它。
字节70~73,视屏屏幕大小,X和Y
只被Paintbrush IV和Paintbrush IV PIus使用;并不是必不可少的,但是对于产生正确的外观比例(防止压缩型失真)可能有用。
三、位图数据
如果没有使用调色板,那么数据是实际的像素值;否则,它们是指向调色板值的指针。在后一种情况下,数据给出的是相对于所使用的调色板的起始处的偏移(比如在三字节的三元组值中,1=字节3)。
当数据是实际的像素值时,它们按色彩平面和扫描行存储。例如,对于三种颜色红、绿和蓝(RGB),数据格式为:
(第0行:)RRRRRR...GGGGGG...BBBBBB...
(第1行:)RRRRRR...GGGGGG...BBBBBB...
如果有两个平面,那么色彩是任选的;如果有三个平面,则颜色为RGB;如果使用四个平面,则它们是符合IBM CGA/EGA标准的单个位的平面:红、绿、蓝和光强(RGBI)。光强位只是给像素以一种名义上较高的亮度。
当数据是指向某调色板指针时,它们就组成一个完整的图像平面(也就是说,它们不会分解成单独色彩平面)。然后数据按如下方式简单地编排(字符P代表各种指针值):
(行0:)PPPPPP
(行1:)PPPPPPP
P的长度取决于深度,以每平面的每像素位数表示。例如,如果深度为4位,则P就是半个字节长。
所有情况下,在扫描行之间都有编码隔断标志。但是,在一个扫描行中的色彩平面间没有编码隔断标志。同样,也没有分隔符可用来标识扫描行的结束(虽然一个扫描行可能是也可能不是用额外的零作为结束)。也就是说,在扫描行之间不会有行号(虽然这里写出来了),也不会有空字符、空格、回车、换行或其他的字符。
不论要记录的是何种类型的位图数据,都使用同样的运行长度压缩方法,下面给出恢复算法(当前普遍使用的基于调色板的图像只有一个平面)。
这里,请您先学习教程,具体了解PCX文件格式,然后来看一下下面这个——例程中由Gif文件转换PCX文件的程序中是如何编写PCX文件头:
WritePcxHeader(FILE *fp,char *palette )
{kk1}
PCXHEAD h;

memset((char *)&h,0,sizeof(PCXHEAD));
h.manufacturer = 0x0a;
h.version = 5;
h.encoding = 1;
h.xmin = h.ymin = 0;
h.xmax = width - 1;
h.ymax = depth - 1;
h.hres = h.vres = 0;
h.palette_type = 1; 
if( BitsPerPixel < 5 ) {kk1}
h.bits_per_pixel = 1;
h.colour_planes = BitsPerPixel;
h.bytes_per_line = pixels2bytes(width);
memcpy(h.palette,palette,(1<}
else if (BitsPerPixel == 8) {kk1}
h.bits_per_pixel = 8;
h.colour_planes = 1;
h.bytes_per_line = width;
}
else {kk1}
h.bits_per_pixel = 8;
h.colour_planes = 1;
h.bytes_per_line = width;
}
fwrite( (char *)&h,1,sizeof(PCXHEAD),fp ); 
}

1、优点 TIF格式的优点主要是适合于广泛的应用程序,它与电脑结构、操作系统和图形处理的硬件无关,它可以处理黑白和灰度图形,允许使用者针对一个扫描器、监视器和打印机的特殊佳能而进行调整。TIF具有防止错误发生的格式,因此,对于媒体之间的数据交换, TIF常常是位元映射的最佳选择之一。
2、缺点 TIF有一个主要的缺点,就是需要花费大量的程序设计工作来进行图形翻译,例如, TIF数据可以用几种不同的方法压缩。为了达到覆盖面更广,一个TIF读取程序必须具有支持这些不同压缩方法的功能。
3、变体 TIF有许多的变体,所以要使数据交换可行,程序设计师要努力使得TIF读取程序可以翻译TIF的各种特性。TlF文件、读取程序和写入程序是根据不问的光度(彩色或灰度)和数据压缩方法而变化的。TIF 5.0定义了四个测光度的TIF级别: TIF-B为色, TIF-G为灰度, TIF-P为基于调色板的彩色,而TIF-K为RGB彩色。 
在这些级别中,象点数据可以用六种压缩格式的任何一种储存,压缩格式通常由一个编号区别,如下所示:
#1 无压缩
#2 CCITTGroup 3,改进的霍夫曼运行长度编码(MOdifed Huffman Run Length Encoding)
#3 传真机相容使用的CCITT Group 3
#4 传真机相容使用的CCITT Group 4
#5 LZW (Lempel-Ziv&Welch)压缩
#32773 PackBits (Macintosh)
1988年8月发行的TIF5.0版本,又增加一些新格式:
#32766 2位元的NeXT RLE(Run Length Encoding)
#32771 类型2的字符对齐版本
#32809 4位元的Thunderscan增量和RLE
#32900 Pixar"picio" RLE
#3290l Silicon Graphics RLE
TIF 6.0版产生于1992年春天,提供了JPEG压缩和其他新功能。
4、综述 TIF是一种国际上非常流行的适于各种电脑和操作系统的图形文件格式,目前国际上流行的很多软件都支持TIF格式。一般扫描器所配备的软件都直接或间接地用到TIF图形文件格式,还有一些软件系统,例如: Windows系统下的PhotoStyler都把TIF作为图形的存取格式, TIF格式也可以转换为Windows的BMP等其它格式。

TIFF格式有三级体系,从高到低依次为:文件头,一个或多个称为IFD的包含桥记指针的目录以及数据。体系的最高层是文件头,只包含三个表项:
(1)一个代码,指明字节顺序(低字节在先还是高字节在先)。
(2)一个把文件标识为TIFF文件的代码号。
(3)一个指向图像文件目录(Image FileDirectory,IFD)的指针。
这里是一个tif文件的文件头:
4D 4D 2A 00 00 00 00 08
IFD提供一系列的指针(索引),这些指针告诉我们各种有关的数据字段在文件中的开始位置,并给出每个字母的数据类型(例如,1字节整型)及长度。这种方法允许数据字段定位在文件的任何地方,可以是差不多任意长度,并可以包含大量信息。例如,一个指针可能指向关于彩色调色板数据的一个786字节字段;另一个可能指向扫描仪的一条64字节灰度修正曲线。在一个文件中可能有几个相关的图像,这时可以有几种IFD。IFD的最后一个表项指向任何一个后续的IFD。
每个指针都有一个标记值指明所指向的数据字段类型的一个代码号。TIFF 规范列出了所有正式的、非专用的标记号,给予它们有用的名字(如SamplesPerPixel,十进制代码为277),并描述这个指针所识别的数据,告知数据的组织方法。

1.优点 一种有竞争力的位元映射格式,为以后扩充说明留有余地。
2.缺点 有许多衍生格式,但井非所有的衍生格式都得到所有应用程序支持。
3.变体 最早的1984年1.0版格式和1989年2.0版格式,应用程序设计师可以注册自己私有的衍生格式。
4.综述 TGA格式由AT&T首先引用,用于支持他们的Targa 和Truevision 公司的 ATVISTA 图形捕捉卡。此格式已经成为数字化图形以及由光跟踪和其它应用程序所产生的高质量图形常用格式。TrueVision公司的TGA文件格式已广泛地被国际上的图形工业所接受。有许多图形工作人员喜欢按个人习惯储存图形文件,并且只存数据,不存格式,使得大量的图形文件成为个人的专用产品,无法交流,因此,有必要按照标准格式来储存图形文件, TGA文件格式就是目前国际上比较流行的图形文件储存格式。

Targa文件以一个固定大小的文件头开始,然后是可变长度的图像标识符(ID)、色彩对应表和图像。 ID字段的偏移为18,紧跟在文件头的后面。
多字节值的存储是低字节(tss)为先,即Intel格式。除了字节对齐之外,值和段均没有填充或对齐。
图像可以彩色映射的。有两种类型的色彩对应表图像:“伪彩色”(pseudo color),这种图像中每个像素值从色彩对应表中选取一个单独值,“直接彩色”(direct color),这种图像中每个像素包含分别查询的红、绿和蓝值。像素为实际的红、绿和蓝值的图像称为“真色彩图”(True Color),为灰度值的称为“黑白图”(black-and-white)。
图像总是按行存储,但行可以按从上到下或从下到上的顺序,而且对像素可队从左到右或从右到左存储,在实际图像中,像素几乎总是从左到右存储。有些型号的扫描仪从上到下扫描,而另外一些扫描仪则从下到上扫描,所以至今还没有占统治地位的行顺序。

1.优点 与设备无关,文件可以很好地组织结构;由于使用向量描述图形的性能,文件可以比相应的位元映射小很多。
2.缺点 语意结构与Windows图形模型关系太密切;文件比较复杂。
3.变体 Windows 3.x将Windows 2.x转换文件格式中加入了新的记录类型,但不使用新类型的转换文件应该与早期版本相容。
4.综述 转换文件(Metafile)是一种图形描述语言,在Metafile中,一个数据记录所在的位置没有什么关系,当要处理图形时,还要利用编译程序将Metafile转换成可见的图形, Windows的Wetafile储存Microsoft Windows图形功能呼叫的一个显示表(Display Lisi),在Metafile中允许包含Windows功能的一个子集合,这个子集合是包含大多数绘图呼叫的一个大子集合。虽然Metafile最初只是为了用作一种图形巨集指令(Macro-instrction),现在它常用作在Windows应用程序之间进行图形交换的格式。

1.优点 由于Autodesk 的Autodesk 在个人电脑上广为流行,所以DXF交换格式得到其它CAD程序的广泛支持,甚至得到其它电脑平台的支持。该标准的公布于世,对于非CAD应用程序存取工程绘图有很大的价值。它具有向量格式所具有的全部优点,再加上作为3D向量格式的优点,因此可以处理真正3D形状,包括线框和立体图形。
2.缺点 图形可用彩色序码给值,使图形与一个256色的表相关联。但彩色表不必与RGB 或其它彩色模组的彩色光谱相关联,读取DXF 的ASCII 格式(过去和现在都很常用的格式)速度太慢,DXF应用程序至少应能处理二维图形计算和文字处埋(如:注释、尺寸标准)。一个完全执行的DXF读取程序必须可以进行字型的生成和演变,以及直线和曲线的产生以及3D形状的2D表达,换句话说。它必须是一个CAD程序。
3.变体 DXF格式有两重格式: ASCII和二进制,由AutoCAD的第10版产生的二进制格式使用二进制编码的序码和数据,而不是ASCII数字,产生的文件比ASCII形成的文件大约要小25%,而且读取速度要快5倍。 AutoCAD有一个用于写DXF文件的选择,其它的CAD程序常常也有这个选择,它将DXF数据限制在实体(形状)中。这样的文件更加紧凑并且能满足图形交换的目的,不过,跟任何一种语言一佯,术语出现的上下文关系才是重要的:序码有许多不同的意义,要根据当时被交流的信息类型而定。例如,序码“10”在描述一个圆时与描述直线时的意义不同。
了解图形文件的基本信息和储存格式,有助于对图形数据的应用、处理(如压缩转换文件格式等),“使各种形式的图形数据能因自由转换而适应不同媒体的表现,这对掌握图形技术而言,虽是最基本的,但却也是非常重要的课程!

DXF与其说是一种图像格式,还不如说是一种语言或一个图形文件,也就是说,文件中数据的确切位置和顺序并不是特别重要。不过踉一种语言一样,术语出现的上下文是很重要的;代码表示许多不同的意义,要根据当时所交流的信息类型,例如代码“10”在描述圆时与描述直线时的意义就不同。
DXF文件由称作“组”的数据对组成。每一组有一个组代码,后面是一个称为组值的数字或字符串:
Group: GROUP CODE
GROUP VALUE
组代码是一个ASCII整数(二进制DXF中则为二进制数),表明后面跟的值的类型。组代码的特定范围为特定类型的数据而保留。例如,范围为0~9的组码表示后回跟的是一个ASCII字符串;特定的码表明该字符串用于什么目的。组代码和数值之间用一个回车/换行符对分开。DXF文件中的数据按照下列方法组织:
HEADER段
包含的信息大多情况下对于非CAD应用程序来说没有任何价值,许多信息是与文本和尺寸标准有关的。有时可将它忽略。
TABLES段
定义某些通用常量,如绘图“层”(layer)、观察角度和距离、坐标系以及尺寸风格。像HEADER段一样,这个段有时也可以忽略。
BLOCKS段
按名字定义实体组,同时它也可以包含实体。现在它还没有得到广泛使用,但由于它允许绘图的模块化,所以变得越来越流行。
ENTITIES段
使用点、线、圆、弧等来定义实际的二维或三维几何体(实体),还包括把实体与层和/或块连接的数据。
由于种种原因,可以跳过HEADER段和TABLES段。交换几何体造型(称为实体)的主要段是ENTITIES段;不过BLOCKS段也可以含有实体。HEADER、TABLES和BLOCKS 段即使为空也常常给出,这是因为应用程序希望有这几个段。在使用时,段就按上面给出的顺序出现。

JPEG(Joint Photographic Experts Group)格式是由ISO和CCITT两大标准组织共同推出的,定义了摄影图像通用的压缩编码方法。是数字化图像的主要存储格式。它是一种压缩位图格式,是目前为止用于摄影图像的最好压缩方法。这种格式的缺点主要是:软件压缩和还原速度慢,格式的标准仍在发展变化,而且由于标准中有可选项,所以存在不兼容的现象。
一般的说,数字化仪产生每个象素24位,红蓝绿各八位。一种典型的压缩格式(如GIF)有256个表项的一张色彩对应表,因此,在所产生的图像种的每一个象素是8位而不是原来的24位,所产生的图像使用256种颜色而不是原来的一千六百万种。而JPEG格式不同,它主要储存颜色变化的信息,特别是高度的变化,因为眼睛对这些变化非常敏感。所以,由JPEG格式存储的图像重建后在亮度上仍然有类似的变化,所以,人眼感觉与原图非常相似。
由JPEG压缩方法而节省的空间是相当大的。例如,一幅727×525的全彩色图像,其原始的每个象素24位格式占用1145KB,它的GIF版本文件为240K,非常高质量的JPEG版本文件是155K,而标准的JPEG版本文件则为58K。

MacPaint图形文件最初并非在PC上执行,而是来自Apple Macintosh,只是在最近,随着具有强大图形功能的MACII的出现, MacPaint图形文件才作为唯一连续使用的“位映射式图像文件格式”在许多方面大放异彩,因为实际上所有涉及图形的MacPaint系统应用程序都接受这种通用文件格式。
当然,在PC方面,情况有所不同。
MacPaint文件的引人之处在于它们的数量是如此之多,无数令人感兴趣的图像均以MacPaint格式存在于公用区域(public domain)中,并且完全没有版权保护。我们可以从bulletin boards中索取这些图像文件,拥有一种即时可得的艺术收集品。
在很大程度上,MacPaint文件格式比其他文件格式缺少灵活性。它的一对象只有黑白两色。如果将一整页MacPaint数据以75点/英寸的分辨率打印出来,则只有一页大小,如果在相当昂贵的激光打印机上以最高分辨率300点/英寸打印出来,也只能得到一幅2×2.5-平方英寸大小的MacPaint图像。
与所有其他通用的图像文件格式不同, MacPaint图像尺寸固定,不论一幅图像的内容是什么,其大小总是576像点(宽)×720像点(长)。
MacPaint格式有许多令人感兴趣的部分,其中可转换成PC格式的版本更是引人如胜。例如,当我们将一幅图像传到Macintosh上的MacPaint应用程序中时,绘图(paint)程序就会显示出38种图样(patterns)及图像本身。如果我们编辑与一幅特定图像相联的图样,则这些图样保留在被编辑状态。一个MacPaint图像文件中包含图像
MacPaint格式的这种特性很独特,想建立一些程序,一般很少用到它。然而,如果您有意设计一个应用这一特性的程序,那些在每个MacPaint图像文件中,就会有38种图样定义。后面有几个程序将会使我们看到这些图样。
在一台Macintosh计算机上,所有文件均以两个chunks的方式来贮存,这些Chunks称为“forks”。它们是数据“fork”及资源“fork”,分别容纳图像信息和程序码。一个MacPaint文件完全由数据“fork”组成,资源“fork”是空的。
当一个MacPaint文件要移植到其他计算机系统(比如一台PC)时,它是作为单个文件被发送的,这个文件包括两个“fork”的内容和一个MacBinary表头(header,表头记录该文件是如何被分割的,以便在将其返回给Macintosh系统时,可重新产生两个“fork”。MacBinary表头中还记录着所移植文件的类型-比方说,是一个MacPaint文件,而不是一个MacWrite文件。Macintosh文件名称可由1到31个Characters组成。
严格他说, MacBinary表头不是文件格式的一部分,实际上,在Macintosh系统的各类文件中,并末将该表头作为一种数据结构。不过,在这里将其视作文件的一部分,主要因为人们在PC上见到的绝大多数MacPaint文件的开始处都有这种表头。
在MacPaint文件中,实际的图像数据位于MacBinary表头及图样数据之后,以及其简单的格式经过压缩。这就是我们首先要研究这种图像文件格式的原因所在。图像数据占据文件的剩余部分。

在一个程序开始分析图像文件之前,首先必须做好一些内部准备工作。举例来说,程序必须弄清文件的实际意义是什么,避免将一个电视游戏的目标程序码作为图像数据来显示。就可支援多种不同规格图像的图像格式而言,还必须确定出某一具体文件的实际大小。所有MacPaint图像大小都一样,所以还原它们的软件不必担心这方面的内部工作。
MacBinary表头中有两个长整数,分别用来定义Macintosh文件类型及文件的创建者(files creator),Mac中有一套独特的系统专用于维护这种数据。每个文件“类型”指定一个4byte程序码(code),这4个bytes既可作为4个byte来处理,也可作为一个长整数来处理,主要根据当时用哪一种方式最方便。每个生成文件的程序还有一个4byte的署名,称为“创建者(creator)”域(field)。MacPaint文件的文件类型是PNTs,如果文件最初是由MacPaint建立,则其“创建者”域为MPNT。然而,由于有多个不同的Macintosh应用程序能够产生MacPaint格式文件,所以我们不能假定“创建者”域就是MPNT。
类型域(Type field)位于MactBinary表头的第0041H到0044H byte之中。这样,如果从这一位置开始的4个byte是PNIG;就可认为这是个MacPaint图像文件,并对其(比如前述的动物图像文件)进行解码。
移植到PC上的Macintosh文件原始名称保存在MacBinary表头的第2至6个byte中,表头中的第一个byte始终是零。文件名称以Pascal语言风格贮存,即字符串中的第一个byte表示该字符串剩余部分的长度。字符甲没有必要以零作为结束标志。
最后,从第132个bytes开始,一直到文件出现,即超过MacBinary表头4个byte,都是图样数据,每个图样为8个bytes长。

做为一种通用的单色图形格式, MacPaint文件的主要缺点很容易列举。它固定的图像尺寸是一个明显的问题,因为大幅图形(特别是那些来自扫描器的)是通用的,并且是人们有兴趣研究的。再者,如果我们把MacBinary表头和它的绘画图样包括在内,那么这种文件格式就要携带一个超大的文件表头。
因此,仅仅有少数几种PC应用程序确实想要接收MacPaint文件。如果有人硬要的话,也有某些PC应用程序会这样做,但是这并非它们的优先选择。
GEM/IMG图像文件是这些应用程序最先的选择之一,它在许多方面鉴于MacPaint文件的优点一有效的图像压缩法,一种易于解码的格式,等等一可以说,它没有什么不足之处。除此之外,IMG格式适用于任何尺寸的图像,其表头是本书讨论的所有文件表头中最小的一个,只有16 byte。
尽管IMG文件具有这些特性,但它们在桌面排版领域之外并不十分流行。如果对PC来说有一种通用的图像文件格式,那么它或许就是PC Paintbrush PCX格式,我们将在下一节详细讨论这种格式。通常IMG格式与Digital Reesearch的有关应用程序以及Ventura Publisher排版软件相联系,这两种软件一个应用了GEM视窗环境,一个应用在改进的GEM排版下。
若要设计一个基于Ventura Publisher软件的图像处理应用程序,就必然会涉及到IMG文件。Ventura以一种独特的方式处理它的图像文件。在桌上排版文件数据中,尽管它声称自己完全可以应付IMG,MacPaint,TIFF以及PCX图像文件,但实际上它只适于处理IMG文件。当请求读入其他三种文件时,每输人一个文件,它都将其转变为IMG格式。这就意味着如果想用Ventura系统处理一个PCX文件,那么需在磁盘上建立两个文件,一个是PCX文件本身,另一个是与它等价的IMG文件,这是一个明显的缺点。当然,最好在Ventura系统中使用其熟悉的文件(比如IMG),这样,即可节省硬件空间又可节省转变时间。

PDF(Portable Document Format)是为了使文档能够在多种平台上阅读而设计的,它是平台无关的,包含了字体、图表、图像和打印控制数据;PDF文件使用了工业标准的压缩算法,通常比PostScript文件小,易于传输与存储;它还是页独立的,可以单独处理各页,特别适合多处理器系统的工作。
PDF文件有三种类型:图像型(位图格式)、标志型(包含电子文本、可标度、索引、搜索、拷贝)和混合型(包含原始图像及识别后的隐藏文本,能保持文档的原状、并可进行文档搜索)
正是由于PDF文件的种种优点,它逐渐成为出版业中的新宠。大家喜欢电子图书,多采用PDF文件格式;而在文档的国际化交流方面,它也已成为Internet上为大家所接受的最好方式。Adobe 公司提供的免费的PDF文件阅读器Acrobat Reader就是很好的阅览器。

在介绍PostScript 文件格式时已述及,PDF文件可以从PS 文件转换而来,那么,这里我们介绍一下其他的生成方式。
上已述及PDF文件有三种类型:图像型、标志型和混合型,其生成方法不尽相同。如利用Acrobat 3.0中的Acrobat Scan可直接借助扫描仪生成图像型PDF文件,而Adobe Capture可在Acrobat Scan的基础上生成标准型和混合型两种PDF文件,而且它是生成混合型PDF文件的唯一途径,但二者处理的对象均是已出版或打印的文档。
对于多数一般用户而言,是希望在接将电子文档转换为PDF文件,那么利用Acrobat 3.0中的Acrobat PDF Writer驱动程序是最易尝试的方法。实际上,安装Acrobat 3.0时,如果选装了该组件,安装完毕,你会发现自己的计算机上多了一个名为“Acrobat PDF Writer”的打印机。启动任何一个支持打印的文档编辑器,选用Acrobat PDF Writer“打印”你的文档(当然别忘了输入文件名及其他一些信息),你就可以得到你那篇文裆的PDF文件了。
如果你已经安装了Word(对Excel也一样), PDF Writer在安装过程中会为Word加载一个宏文件。安装PDF Writer后你可看到Word中新增的工具条,在Word的“文件”菜单下,你也会发现一个新选项:“Creat Adobe PDF”。
另外,如果你发现用PDF Writer生成的文件质量不好,那么建议你先生成PS文件,然后用Acrobat Distiller转换为PDF文件,质量是会有所改善的。如果你手中有Acrobat 3.0这也是生成PDF文件的一个好方法,当然,那些影响打印质量的参数,就需要你自己在PostScript打印机属性里去尝试了。
下面我们再来介绍一下如何从PDF文件中提取纯文本。
随着电子书刊的流行和PDF文件运用场合的增多,不少人在阅读PDF文件之余,还希望能把文字内容“剪裁”下来,进行一些纯文本的处理工作,比如编辑或引用等。这就遇到了PDF文件的“文本化”问题。
不论是Acrobat Reader还是Acrobat 3.0软件包,都没有提供将PDF文件转换为文本文件的功能。我们一般能够作到的,是利用Acrobat Reader提供的文本选取功能(在工具栏上可以找到一种selects the text selection tool),将所需要的文本内容COPY下来。这种功能适用于处埋有限的文本,复制较大篇辐的内容则比较麻烦。同时,PDF文件在制作生成的时候,是可以对阅读者的访问权限进行限制的,比如,可以禁止打印,也可以禁止使用文本选取功能。

Windows以其直观、友好的图形用户界面和多任务多窗口的操作环境,吸引了越来越多的计算机用户和生产厂家,目前Windows已成为PC机窗口环境的标准。Windows对多媒体的支持是从对Windows 3.0增加多媒体扩展部分开始的,随后于1992年4月推出支持多媒体的Windows 3.1。Windows 3.1主要支持多媒体中的音频和静态图象,同时也增加了动画功能。
对于视频图象,Windows本身没有提供处理功能,但是Windows 3.1的多媒体控制接口MCI(Multimedia Control Interface)使Windows具有设备独立性,从而允许多媒体硬件生产厂家用DDK开发Windows下的驱动程序,将多媒体硬件加到Windows中,从而实现视频功能。MCI是一个介于Windows与硬件设备间的协议层,通过它Windows就使应用程序可与任何有多媒体扩展驱动程序的设备相配合。DVI系统就是MCI在Windows下的使用。1993年1月,Microsoft公司与Intel公司、IBM公司等合作,结合Microsoft的AVI(Audio Video Interleaved)标准与Intel的Indeo Video技术,发布了Video for Windows,使得任何PC机都能在无特定硬件支持的条件下播放视频画面。Video for Windows是基于AVI的一组应用程序。
Microsoft公司的AVI格式是一个RIFF文件说明,它用于获取、编辑以及演示音频/视频序列。一般说来,AVI文件包含了不同类型的数据流。许多AVI序列将用到音频和视频流。一个简单的略经变化的AVI序列可能用到视频数据而不需要音频数据,特殊的AVI序列也许还包括一个控制路径(control track)或者MIDI路径作为附加的数据流。控制路径能控制外部设备诸如一个MCI视频显示器。MIDI路径能够为结果提供背景音乐。一个特殊的序列需要一个特别的控制程序,这个特殊的控制程序不仅能够利用它的功能来阅读和演示AVI序列,而且能阅读和演示特殊文件中的AVI序列,这些应用忽略特殊文件中的非AVI数据。这里介绍的AVI文件是只包括视频和音频数据的文件。内容主要包括以下部分:
·AVI文件必需块
·AVI文件任选块
·写AVI文件的子程序的方法
·在Windows中播放AVI文件的代码

Microsoft制定的AVI标准格式是一种软件辅助的数字化视频压缩技术,使用它能快速地从一个数字存储设备中解压缩图象。此标准与CD-ROM的流通速率(150KB/S)相一致。AVI文件的存储格式采取了Audio和Video交错存储的方式,这种交错的存储保证了视频画面与音频同步。
AVI文件用的是AVI RIFF形式,AVI RIFF形式由字串“AVI”标识。所有的AVI文件都包括两个必须的LIST块。这些块定义了流和数据流的格式。AVI文件可能还包括一个索引块。这个任选的块给出了文件中这些数据块的地址。一个具有上述内容的AVI文件具有以下形式:
RIFF ( 'AVI'
LIST ('hdrl'
}
LIST (' movi '
)
[' idxl' ]
}
其中LIST块的索引块都是RIFF'AVI' 块的子块,'AVI' 块标识了此文件是一个AVIRIFF文件。LIST块“hdrl”定义了数据的格式,它是必须出现的LIST块中的第一块。 LIST块' movi' 包含了AVI序列的数据,是必须出现的LIST块中的第二块。'idxl' 是任选的索引块。AVI文件中这三项内容必须按适当的次序排列。
RIFF ( ' AVI '
LIST (' hdrl '
' avih' (

)
LIST ( ' Strl '
' strh' ( )
' strf' ( )
' strd' (additional header data)
)
)
LIST (' movi ' 
{kk1}SubChunk | LIST('rec'
SubChunk1
SubChunk2
)
}

['idxl']

LIST块' hdrl' 以及' movi' 运用的是子块形式。从以下例子可以看出扩充后的AVIRIFF形式包含有完整的LIST块' hdrl' 和' movi' 。

                                                

     WINDOWS编程基础

  


   随着PC机上图象处理软件及显示卡技术的发展,使苹果Mac(麦金托什)机独霸图象处理的局面被打破。如今在PC机上处理高质量图象已不再是梦想,丰富的图象处理软件如Photo Styler、Corel Draw、Adobe Photoshop、Paintbrush、Freehand、Imagepal、Picture Publisher等等,其处理图象的能力和水平已直追一些专业图形图象工作站。加上多媒体技术的推广发展,使图象处理成为当今多媒体应用中的一项重要内容。 
Windows提供的三大主要功能中,标准的图形用户界面最引人注目,而且对用户也是最重要的。在它所提供的用户界面中使用图标来代表驱动器、文件、子目录和许多操作系统的命令及操作。
    因为所有的Windows程序都具有类似的结构和相同的“外观”,所以用户不需花费很长的时间来学习新程序的用法。对于编程人员而言,具有这种一致的用户界面是因为直接使用Windows的内部子模序来创建用户界面的基本元素(菜单、对话框、滚动条等)。所有的菜单都具有同样的键盘和鼠标接口,这是因为Windows自己处理这项工作,而不是把它交给应用程序去完成。
    Windows的多任务操作环境允许用户同时运行多个应用程序,或在同一个程序中同时“并行”完成几件事情。一个典型的例子是在Windows中运行四个应用程序,每个程序在屏幕上占一个矩形区域。用户可在任何时间移动屏幕上的窗口、改变窗口大小、从一个窗口转换到另一个窗口和修改窗口内的信息。
    虽然例程里的这个例子有四个并发执行的进程,但是在任何时候只有一个进程在实际使用处理器。并发运行的任务可以有任意多个,由Windows负责把处理器时间分配给多个任务,Windows根据排好的输入队列及其他信息来控制微处理器时间的分配。
在多任务操作系统未出现之前,应用程序独自控制计算机的所有资源(输入/输出设备、内存、显示器和CPU)。而在Windows中,所有这些资源都是共享的。
    内存是Windows中最重要的共享资源之一,当多个应用程序在同一时间运行时,这些应用程序共享内存资源,如果程序运行结束,则归还其所占用的内存区域。为了提供大块内存区域,Windwos能在内存中搬移整块代码和数据,把存储碎片收集起来以形成大块连续的内存区域。Windows在内存管理方面的一个最明显特征是它把软件开发者从DOS的内存限制中解放出来。
    在用户并发运行的几个程序都调用同一个程序时,为了节省空间,Windows共享那一段相同的代码,在Windows中运行的程序甚至可以共享其他.EXE文件中的例程。在Windows中把包含这种共享的例程的库文件称为动态链接库(DLL),Windows在运行时可把程序与动态链接库中的例程链接起来。为此,Windows程序使用了一种新的.EXE文件格式,这种文件包括了Windows执行时所需要的信息,如管理代码段和数据段以及用于动态链接的必要信息。

    在开始介绍Windows图形程序设计之前,这里我想先介绍一些Visual C++图形编程常见的问题和一些基本方法和技巧:
一、在用户环境中确定系统显示元素的颜色 
调用SDK函数GetSysColor可以获取一个特定显示元素的颜色。下例说明了如何在MFC函数CMainFrameWnd:: OnNcPaint中调用该函数设置窗口标题颜色。 
void CMiniFrameWnd:: OnNcPaint () 
{ kk1}
… 
dc.SetTextColor (:: GetSysColor (m_bActive ? 
OLOR_CAPTIONTEXT : COLOR_INACTIVECAPTIONTEXT)); 
… 

二、访问预定义的GDI对象 
可以通过调用CDC:: SlectStockObject使用Windows的几个预定义的对象,诸 如刷子、笔以及字体。下例使用了Windows预定义的笔和刷子GDI对象在视窗中画一个椭圆。 
//Draw ellipse using stock black pen and gray brush. 
void CSampleView:: OnDraw (CDC* pDC) 
{ kk1}
//Determine size of view. 
CRect rcView; 
GetClientRect (rcView); 
//Use stock black pen and stock gray brush to draw ellipse. 
pDC->SelectStockObject (BLACK_PEN); 
pDC->SelectStockObject (GRAY_BRUSH) 
//Draw the ellipse. 
pDC->Ellipse (reView); 

也可以调用新的SDK函数GetSysColorBrush获取一个系统颜色刷子,下例用背景色在视窗中画一个椭圆:
void CsampleView:: OnDraw (CDC* pDC) 
{kk1} 
//Determine size of view. 
CRect rcView; 
GetClientRect (rcView); 
//Use background color for tooltips brush. 
CBrush * pOrgBrush=pDC->SelectObject
(CBrush::FromHandle(::GetSysColorBrush (COLOR_INFOBK))); 
//Draw the ellipse. 
pDC->Ellipse (rcView); 
//Restore original brush. 
pDC->SelectObject (pOrgBrush); 

三、确定GDI对象的属性信息 
可以调用GDIObject:: GetObject。这个函数将指定图表设备的消息写入到 
缓冲区。下例创建了几个有用的辅助函数。 
//Determine if font is bold. 
BOOL IsFontBold (const CFont&font) 
{kk1} 
LOGFONT stFont; 
font.GetObject (sizeof (LOGFONT), &stFont); 
return (stFont.lfBold)? TRUE: FALSE; 


//Return the size of a bitmap. 
CSize GetBitmapSize (const CBitmap&bitmap) 
{ kk1}
BITMAP stBitmap; 
bitmap.GetObject (sizeof (BITMAP), &stBitmap); 
return CSize (stBitmap.bmWidth, stBitmap. bmHeight); 

//Create a pen with the same color as a brush. BOOL CreatePenFromBrush (Cpen&pen, cost Cbrush&brush) 
{kk1} 
LOGBRUSH stBrush; 
brush.Getobject (sizeof (LOGBRUSH), &stBrush); 
return pen. Createpen (PS_SOLID, 0, stBrush.ibColor); 

四、实现一个橡皮区矩形 
CRectTracker是一个很有用的类,可以通过调用CRectTracker::TrackRubberBand 
响应WM_LBUTTONDOWN消息来创建一个橡皮区矩形。下例表明使用CRectTracker移动和重置视窗中的蓝色椭圆的大小是很容易的事情。 
首先,在文件档中声明一个CRectTracker数据成员: class CSampleView : Public CView 
{ kk1}
… 
public : 
CrectTracker m_tracker; 
… 
}; 
其次,在文档类构造函数中初始化CRectTracker对象: CSampleDoc:: CSampleDOC () 
{ kk1}
//Initialize tracker position, size and style. m_tracker.m_rect.SetRect (0, 0, 10, 10); 
m_tracker.m_nStyle=CRectTracker:: resizeInside | CRectTracker:: dottedLine; 

然后,在OnDraw函数中画椭圆和踪迹矩形: 
void CSampleView:: OnDraw (CDC* pDC) 
{ kk1}
CSampleDoc* pDoc=GetDocument (); 
ASSERT_VALID (pDoc); 
//Select blue brush into device context. CBrush brush (RGB (0, 0, 255)); 
CBrush* pOldBrush=pDC->SelectObject (&brush); 
//draw ellipse in tracking rectangle. 
Crect rcEllipse; 
pDoc->m_tracker.GetTrueRect (rcEllipse); 
pDC->Ellipse (rcEllipse); 
//Draw tracking rectangle. 
pDoc->m_tracker.Draw (pDC); 
//Select blue brush out of device context. 
pDC->Selectobject (pOldBrush); 

最后,使用ClassWizard处理WM_LBUTTONDOWN消息,并增加下述代码。该段代码根据鼠标击键情况可以拖放、移动或者重置椭圆的大小。 
void CSampleView::OnLButtonDown (UINT nFlags, CPoint point) 
{ kk1}
//Get pointer to document. 
CSampleDoc* pDoc=GetDocument (); 
ASSERT_VALID (pDoc); 
//If clicked on ellipse, drag or resize it. Otherwise create a rectangle
//rubber-band rectangle nd create a new ellipse. BOOL bResult=pDoc->m_tracker.HitTest (point)!= 
CRectTracker::hitNothing; 
//Tracker rectangle changed so update views. 
if (bResult) 
{ kk1}
pDoc->m_tracker.Track (this,point,TRue);
pDoc->SetModifiedFlag (); 
pDoc->UpdateAllViews (NULL); 

else pDoc->m-tracker.TrackRubberBand (this,point,TRUE); 
CView:: onLButtonDown (nFlags,point); 


在计算机显示系统中,只有高档的显示系统才可以在屏幕每一个位置上同时显示各种可能的颜色,大多数显示卡只能显示有限数量的颜色。标准VGA能同屏显示16色,每像素占4位存储空间。内存足够的SVGA适配器最多每像素占8位,可同屏幕显示262144种不同颜色中的256色。普通使用的SVGA显示适配器通过将像素值解释为一个表格的索引,根据索引值将每个像素的内容转变为一种颜色。这个表格就是硬件调色板,其条目就是RGB值。
Windows 系统在支持硬件调色板的时候,遇到了些困难。如果 Windows 系统允许任何一个程序改变视频硬件调色板中RGB 颜色的设置,那么在系统中运行的每一个应用程序都将受影响。例如硬件调色板中的黑色被改为蓝色,那么每一个窗口中的所有黑色像素立即就会被改为蓝色。这就违反了 Windows 应用程序作为独立窗口运行,彼此互不干扰的基本原则。另一个问题是 Windows 程序可以在任何系统运行,而大多数系统中显示的颜色和 Super VGA 系统中的颜色数量不同。
为了解决上述问题,从3.0版开始Windows提供了一个调色板管理器,用作用户开发的应用程序和输出设备。
调色板管理器由系统调色板和逻辑调色板构成,它们用于管理实际设备的“硬件调色板”。系统调色板是相应于显示器的硬件调色板,它是一个有二十种保留颜色的数组,它的项数与显示器实际能显示的颜色数相等,这些保留颜色被用来绘制系统的菜单、按钮控制、屏幕桌面颜色和抖动的画刷等。 
一般情况下,这二十种保留颜色不能被改变。逻辑调色板是模拟显示卡中硬件调色板的一块内存区域,逻辑调色板中每一项包含一个TGB值,这个RGB值供应用程序创建彩色的画笔、字体、画刷和位图。当逻辑调色板包括的项比硬件设备实际支持的要多时,“扩充的”逻辑调色板就匹配硬件调色板的相近颜色,如果逻辑调色板所包括的项少于硬件调色板,硬件调色板中的一些颜色将不使用。
为了更加深入的了解Windows系统中的调色板,这里我们再来看一下调色板管理器的工作原理:
如果Windows显示器处理的是24位彩色,调色板管理器没有太大意义,因为可以显示的颜色(约有一千六百万种)能表示DIB中所有颜色。即使显示器处理的只是3位或4位颜色,调色板管理器也没有什么用处,这时系统本身储存有20种系统颜色,已超过了设备能显示的颜色数组。只有对八位(256种颜色)彩色显示器,调色板管理器才真正起作用。为了使调色板管理正常工作,当Windows应用程序使用逻辑调色板时,首先必须检查显示驱动器是否支持逻辑调色板,然后根据需要创建一个逻辑调色板,在使用逻辑调色板之前还必须将其先安装到系统调色板上。具体过程如下:
1) 检查显示驱动程序是否支持逻辑调色板
if(GetDeviceCaps(hDC,DRIVERSION)==0x300)
{kk1}
}
else{kk1}
//不支持逻辑调色板
}
2)逻辑调色板的创建
首先根据Windows规定的逻辑调色板的格式,在内存中分配一块相应大小的区域。内存块的大小根据需要显示的颜色数所定。
typedefstructtagLOGPALETTE{kk1}
WORDpalVersion;
/*windows的版本号*/
WORDpalNumEntries;
/*调色板的项数*/
PALETTEENTRYpalPalEntry[1];
}LOGPALETTE;
typedefstructtagPALETTEENTRY{kk1}
BYTEpeRed;
BYTEpeGreen;
BYTEpeBlue;
BYTEpeFlags;
}PALETTEENTRY;
公式如下:
Sizeof(LOGPALETTE)*numcolor*Sizeof(PALETTEENTRY)
numcolor-----------显示的颜色数;
然后将Windows系统的版本号,及调色板要显示的颜色数填入LOGPALETTE结构中;再将所需要显示的颜色的RGB值依次填入PALETTEENTRY结构中。最后,利用Windows的API中的CreatePalette()函数创建一个调色板,并利用函数SelectPalette()把创建的调色板选入设备描述表。这样一个逻辑调色板就创建了。





(3)逻辑调色板映射到系统调色板
API中RealizePalette()函数可以使Windows的调色板管理器工作。RealizePalette()函数通知Windows调色板管理器真正地改变系统调色板,使其与设备描述表中的逻辑调色板相匹配并按要求映射颜色。这个映射过程不是简单的将逻辑调色板前面的颜色放入系统调色板二十中保留颜色之后,逻辑调色板中剩余的颜色就抛弃不管。它的映射规则如下:
如果逻辑调色板中某种颜色在系统调色板中已存在,该颜色将映射到系统调色板中相应颜色上。
如果逻辑调色板中某种颜色在系统调色板中无相应颜色,只要系统调色板有空间,该颜色就加到系统调色板上。
当系统调色板已满时,逻辑调色板中颜色映射到系统调色板中最相近的颜色上。根据以上规则映射就形成了Windows用来正确显示图象的调色板。
通常情况下,Windows系统保留二十种供所有窗口使用的系统颜色不能被改变,但是系统还是提供了函数SetSystemPaletteUse()允许应用程序去改变系统调色板,或者恢复正常状态。在使用一个16到64色的设备时,改变系统调色板是扩展颜色选择项的一种方法。对于64色以上的设备来说,具有足够的颜色。
因此,保留系统的颜色与增加需要的颜色并不矛盾。
(4)在退出程序前,释放逻辑调色板分配的内存,恢复原来的调色板,删除当前调色板。

LocalUnlock(hLocPal);
LocalFree(hLocPal);
SeletPalette(hDC,hOldPal,FALSE);
DeleteObject(hNewPal);

三、逻辑调色板冲突的解决方法
当几个程序都使用逻辑调色板的时候,Windows系统把当前优先级最高窗口的逻辑调色板送给系统调色板。非活动窗口的颜色取决于还有哪些颜色未被采用,这些非活动窗口首先使用系统调色板中剩余不用的项,然后才为那些无法得到原色的颜色使用调色板上相近的颜色。当要在同一窗口显示具有不同逻辑调色板的设备无关位图时,Windows系统就无法支持。我们解决这个问题的办法是:合并所有的逻辑调色板。当逻辑调色板的总项数不超过256时,合并操作只需将所有的逻辑调色板连接在一起;当总项数超过256时,可采用以下方法来合并逻辑调色板:
从不同逻辑调色板中选出最常用的颜色,组成一个256项逻辑调色板。
反复地合并不同逻辑调色板中相邻颜色并用其平均值来取它们。这样就把所有逻辑调色板简化成一个相似的256色逻辑调色板。



与内存一样,键盘和鼠标输入也是Windows的共享资源,Windows下的C程序不能通过调用getchar函数直接从键盘上读取字符。在Windows中,应用程序不直接说明是从键盘还是从鼠标器读取输入消息,而是从被称作系统队列的键盘、鼠标和时钟等接受所有的输入消息,然后再由此队列负责把输入消息重新分配到各个应用程序中,即把系统队列中的消息复制到应用程序的相应队列中,也就是把输入重定向到相应的应用程序,此时如应用程序准备处理输入,它就从自己的队列中读取消息。
键盘、鼠标和时钟的所有信息都具有同一格式,并且用同样的方式进行处理,另外,Windows给每个消息提供一个与设备无关的虚拟键码来标识该键,键盘产生与设备有关的扫描码。作为共享资源的键盘和鼠标,为Windows下运行的所有应用程序提供输入,从键盘输入的所有消息都直接送到当前处于激活状态的窗口,而与鼠标有关的消息则送到鼠标光标所指的窗口中。这里的消息具有特定的含义,下面简要介绍。
Windows的消息系统是多任务执行环境的基础之一,它是发送消息的基本结构。从编程者的角度看来,一条消息可被视为某一事件的发生(如按下或移动鼠标、键盘击键),这些事件既可以由用户引发,也可以由应用程序产生,甚至Windows本身也能发出消息。
Windows中的消息主要有两个作用: (l)Windows通过它的消息系统实现多任务功能,支持多个任务同时并发执行。这一消息系统使得多个应用程序有可能共享一个物理处理器,每次Windows向应用程序传送消息的同时也分配该程序一段CPU时间。(2)使应用程序对该环境中的事件发生响应。每当一个事件发生时,Windows都记录一次,并把一个适当的消息发送给对此事件感兴趣的各个应用程序。所以从最基本的层次看,Windows应用程序的一个主要任务是处理消息。

Windows的第三个主要特点是设备独立性。一个应用程序可能要与多种硬件设备接口,例如显示器有多种类型,怎样才能使用户开发的应用程序可以不受设备变更的影响呢?
非Windws环境(如DOS)下开发的应用程序必须为每种叶能用到的设备写驱动程序。假定现在要编写一个适用于各种打印机的图形打印程序,程序员必须为每种打印机编写不同的驱动程序。我们都知道,打印机的种类很多,如Epson LQ-l500、Epson LQ-1600、HP LaserJet、HP Deskjet等,显然这样做工作量很大。
在Windows环境下,每种设备驱动程序——不管是显示器,还是打印机、键盘、鼠标——只需写一次。这样就不需要每个软件开发人员去编写自己的设备驱动程序,只要硬件生产厂商为其产品编写一个设备驱动程序,由他们把这些设备驱动程序连同设备一起提供给用户即可。Windows在安装时,已包括了系统现有的每一个设备的驱动程序,每当应用程序发出打印或画图命令时,Windows就通过适当的驱动程序输出打印数据或图形。把用户所需的设备驱动程序直接装入系统,从而省去许多重复琐碎的编程工作。
该功能使Windwos应用的开发人员很容易开发自己的应用程序。应用程序只要与Windows打交道,而不必管任何具体的输入/输出设备,不必知道所用的打印机或图形显示器的型号规格。Windows为实现这种与设备无关的特性,规定与Windons接口的硬件须具备几种规定功能,它们是使用软件开发工具包(SDK)时保证相应的例程能正常工作的最基本功能。对于每类设备有一组含义不同的功能定义,你可参阅相应的Windows手册。

Windows的许多功能是由被称作是DLL的动态链接库提供的。DLL提供一个强大且灵活的图形用户口,从而增强了基本的操作系统。DLL中的预定义函数不是当.EXE文件生成时静态产生的,而是当应用程序动态装入时才与之相连的。这样做显然节省了内存,不管有多少应用模序在运行,同一时间RAM中只有程序库的一个备份。
Windows改变了程序库的格式,因而有更强的通用性,它们不仅保留了其他DOS执行文件的同样格式,而且可包含任何程序可包含的内容。除了函数,程序库也可以编码数据甚至并入图形资源(如光标形状和位图)。Windows程序库扩展了共享资源的范围,并且为程序员节省了更多时间。
从技术角度讲,当一个Windows应用程序调用一个Windows函数时,编译器必须为此函数产生一个远程调用该函数的机器码,这个函数位于Windows程序库的代码段。于是出现了这样一个问题:程序在Windows中真正运行之前,Windows函数的地址是未知的。为解决这一问题,Windows提供了延迟联编(也称动态链接)技术。使用MSC6.0版中的LINK或Pascal的链接程序即可完成这个任务。包含在SDK中的是特殊的“入口程序库”,用于为Windows程序的动态链接作准备。许多Windows的小模式程序都要用到LIBW.LIB和SLIBW.LIB这两个入口程序库。SLIBW.LIB程序为包含程序中可能调用的所有Windows函数的记录,这个记录定义了各个函数所在的Windows模块。
假设有一个Windows应用程序要调用Windows中的PostMessage函数,当链接该程序时,链接程序在SLIBW.LIB中的表中找到PostMessage函数,取出该函数的序号并把这一信息嵌人程序的.EXE文件中。程序运行时,Windows再把程序的函数调用与真正的Post Message函数链接起来。

旧的MS―DOS可执行文件的头标格式中没有地方可用来嵌入附加的动态链接信息,在Windows中采用的是一种新的.EXE文件格式,这种格式有一新文件头标。
Windows程序模块包括KERNEL、USER和GDI,其中KERNEL完成内存管理、程序的装人与执行和任务调度等功能,它需要调用原MS―DOS中的文件管理、磁盘输入输出和程序执行等功能;USER是一个程序库,它用来对声音、时钟、鼠标器及键盘输入等操作进行管理;GDI是一功能十分丰富的子程序库,它提供了图形与文字输出、图象操作和窗口管理等各种与显示和打印有关的功能。Windows程序模块中的例程可帮助Windows中运行的程序执行多种操作,如发送和接收消息。上述KERNEL、USER和GDI模块中的库函数可被应用程序调用,也可被其他程序模块调用。把包含库函数的模块称为输出者(export)。新的可执行格式中有一个入口表用于指明模块内每个输出函数的地址。
从应用程序的角度看,用到的库函数被认为是入口 (import)函数。应用程序对一个入口函数发出的远程调用可用不同的重定位表来确定。几乎所有的应用程序都至少包含一个入口库函数,或者称为被外部调用的函数。该Windows库函数一般来自某个程序库模块,用于从Windows 接收消息,该函数的使用标志必须是“export”,这才能使Windows允许它被一个外部模块正常调用。
新的可执行格式还为程序或程序中每个代码段和数据段提供了附加的信息。一般说来,代码段标记成“可移动的”和“可丢弃的”,而数据段标记成“可移动的”。这就允许Windows在内存中移动代码段和数据段,当内存不够用时可以把代码段丢弃掉(因为代码段被标成是“可丢弃的”)。如果在后面又需要一个已被丢弃掉的代码段,只需简单地从初始的.EXE文件中把该代码段重新装入。
Windows中还有一种执行格式为“调用时装入”。在这种格式下,它把程序或程序库定义为:如果没有其他代码调用该函数,则不把它的代码段装入内存。这一完善的内存管理技术允许Windows在正常情况下只够运行一个程序的内存空间中同时运行多个应用程序。

Windows应用程序是事件驱动(或称作消息驱动)的应用程序。Windows是一个多任务的操作系统,也就是说,在同一时刻,在Windows中有着多个应用程序的实例正在运行,在这样的一个操作系统中,不可能像过去的DOS那样,由一个应用程序来享用所有的系统资源,这些资源是由Windows统一管理的。
那么,特定的应用程序如何获得用户输入的信息呢?事实上,Windows时刻监视着用户的一举一动,并分析用户的动作与哪一个应用程序相关,然后,将用户的动作以消息的形式发送给该应用程序,应用程序时刻等待着消息的到来,一但发现它的消息队列中有未处理的消息,就获取并分析该消息,最后,应用程序根据消息所包含的内容采取适当的动作来响应用户所作的操作。
举一个例子来说明上面的这个问题,假设我们编了一个程序,该程序有一个File菜单,那么,在运行该应用程序的时候,如果用户单击了File菜单,这个动作将被Windows (而不是应用程序本身!)所捕获,Windows经过分析得知这个动作应该由上面所说的那个应用程序去处理,既然是这样,Windows就发送了个叫做WM_COMMAND的消息给应用程序,该消息所包含信息告诉应用程序:“用户单击了File菜单”,应用程序得知这一消息之后,采取相应的动作来响应它,这个过程称为消息处理。Windows为每一个应用程序(确切地说是每一个线程)维护了相应的消息队列,应用程序的任务就是不停的从它的消息队列中获取消息,分析消息和处理消息,直到一条接到叫做WM_QUIT消息为止,这个过程通常是由一种叫做消息循环的程序结构来实现的。

一个典型的Windows应用程序的包括四个普通段—WinMain( )函数、消息循环、窗口过程及窗口消息。
WinMain( )函数是所有Windows程序的入口点、出口点WinMain函数包含三个基本部分:过程说明、程序初始化及消息循环。
WinMain( )函数和过程说明如下:
int PASCAL WinMain(HANDLE hInstance,HANDLE hPrevInstance, LPSTR lpszCmdLine, int cmdShow);
其中hInstance是标识程序的句柄(handle)。 hPrevInstance指明和哪一个实例(hInstance)相关——如果有的话。若程序具有同样的模块名,则认为程序是相关的。指明用于程序的命令行参数,cmdShow指明第一次打开窗口时主窗口的状态。
程序初始化部分是winMain( )函数的主体,利用winMain( )函数的程序初始化部分创建一个窗口。在创建窗口的过程中,必须定义窗口类。
WinMain( )函数的初始化部分的第一段定义WndClass数据结构的各个成员。在这部分代码段中,定义:
用于窗口的光标
用于填充窗口背景的刷子
窗口过程的名字
窗口类的风格,窗口主莱单
类名

在开始介绍Windows图形程序设计之前,这里我想先介绍一些Visual C++图形编程常见的问题和一些基本方法和技巧:
一、在用户环境中确定系统显示元素的颜色 
调用SDK函数GetSysColor可以获取一个特定显示元素的颜色。下例说明了如何在MFC函数CMainFrameWnd:: OnNcPaint中调用该函数设置窗口标题颜色。 
void CMiniFrameWnd:: OnNcPaint () 
{ kk1}
… 
dc.SetTextColor (:: GetSysColor (m_bActive ? 
OLOR_CAPTIONTEXT : COLOR_INACTIVECAPTIONTEXT)); 
… 

二、访问预定义的GDI对象 
可以通过调用CDC:: SlectStockObject使用Windows的几个预定义的对象,诸 如刷子、笔以及字体。下例使用了Windows预定义的笔和刷子GDI对象在视窗中画一个椭圆。 
//Draw ellipse using stock black pen and gray brush. 
void CSampleView:: OnDraw (CDC* pDC) 
{ kk1}
//Determine size of view. 
CRect rcView; 
GetClientRect (rcView); 
//Use stock black pen and stock gray brush to draw ellipse. 
pDC->SelectStockObject (BLACK_PEN); 
pDC->SelectStockObject (GRAY_BRUSH) 
//Draw the ellipse. 
pDC->Ellipse (reView); 

也可以调用新的SDK函数GetSysColorBrush获取一个系统颜色刷子,下例用背景色在视窗中画一个椭圆:
void CsampleView:: OnDraw (CDC* pDC) 
{kk1} 
//Determine size of view. 
CRect rcView; 
GetClientRect (rcView); 
//Use background color for tooltips brush. 
CBrush * pOrgBrush=pDC->SelectObject
(CBrush::FromHandle(::GetSysColorBrush (COLOR_INFOBK))); 
//Draw the ellipse. 
pDC->Ellipse (rcView); 
//Restore original brush. 
pDC->SelectObject (pOrgBrush); 

三、确定GDI对象的属性信息 
可以调用GDIObject:: GetObject。这个函数将指定图表设备的消息写入到 
缓冲区。下例创建了几个有用的辅助函数。 
//Determine if font is bold. 
BOOL IsFontBold (const CFont&font) 
{kk1} 
LOGFONT stFont; 
font.GetObject (sizeof (LOGFONT), &stFont); 
return (stFont.lfBold)? TRUE: FALSE; 


//Return the size of a bitmap. 
CSize GetBitmapSize (const CBitmap&bitmap) 
{ kk1}
BITMAP stBitmap; 
bitmap.GetObject (sizeof (BITMAP), &stBitmap); 
return CSize (stBitmap.bmWidth, stBitmap. bmHeight); 

//Create a pen with the same color as a brush. BOOL CreatePenFromBrush (Cpen&pen, cost Cbrush&brush) 
{kk1} 
LOGBRUSH stBrush; 
brush.Getobject (sizeof (LOGBRUSH), &stBrush); 
return pen. Createpen (PS_SOLID, 0, stBrush.ibColor); 

四、实现一个橡皮区矩形 
CRectTracker是一个很有用的类,可以通过调用CRectTracker::TrackRubberBand 
响应WM_LBUTTONDOWN消息来创建一个橡皮区矩形。下例表明使用CRectTracker移动和重置视窗中的蓝色椭圆的大小是很容易的事情。 
首先,在文件档中声明一个CRectTracker数据成员: class CSampleView : Public CView 
{ kk1}
… 
public : 
CrectTracker m_tracker; 
… 
}; 
其次,在文档类构造函数中初始化CRectTracker对象: CSampleDoc:: CSampleDOC () 
{ kk1}
//Initialize tracker position, size and style. m_tracker.m_rect.SetRect (0, 0, 10, 10); 
m_tracker.m_nStyle=CRectTracker:: resizeInside | CRectTracker:: dottedLine; 

然后,在OnDraw函数中画椭圆和踪迹矩形: 
void CSampleView:: OnDraw (CDC* pDC) 
{ kk1}
CSampleDoc* pDoc=GetDocument (); 
ASSERT_VALID (pDoc); 
//Select blue brush into device context. CBrush brush (RGB (0, 0, 255)); 
CBrush* pOldBrush=pDC->SelectObject (&brush); 
//draw ellipse in tracking rectangle. 
Crect rcEllipse; 
pDoc->m_tracker.GetTrueRect (rcEllipse); 
pDC->Ellipse (rcEllipse); 
//Draw tracking rectangle. 
pDoc->m_tracker.Draw (pDC); 
//Select blue brush out of device context. 
pDC->Selectobject (pOldBrush); 

最后,使用ClassWizard处理WM_LBUTTONDOWN消息,并增加下述代码。该段代码根据鼠标击键情况可以拖放、移动或者重置椭圆的大小。 
void CSampleView::OnLButtonDown (UINT nFlags, CPoint point) 
{ kk1}
//Get pointer to document. 
CSampleDoc* pDoc=GetDocument (); 
ASSERT_VALID (pDoc); 
//If clicked on ellipse, drag or resize it. Otherwise create a rectangle
//rubber-band rectangle nd create a new ellipse. BOOL bResult=pDoc->m_tracker.HitTest (point)!= 
CRectTracker::hitNothing; 
//Tracker rectangle changed so update views. 
if (bResult) 
{ kk1}
pDoc->m_tracker.Track (this,point,TRue);
pDoc->SetModifiedFlag (); 
pDoc->UpdateAllViews (NULL); 

else pDoc->m-tracker.TrackRubberBand (this,point,TRUE); 
CView:: onLButtonDown (nFlags,point); 


在Delphi中,专门定义了一组对象和部件用以绘制图形,完成一些简单的图像功能。利用这些对象、部件的方法,可以方便地绘制各种常用图形;通过设置它们的属性,能得到不同风格的图形。另外,通过对鼠标事件的定义,可以方便的设计图形绘制程序。
TCanvas Object(画布对象)
TCanvas对象是一个用于绘图的表面,在这个区域上,程序可实现各种绘图功能,很多部件(如TIMage,TMemo)的Canvas属性就是TCanvas对象。在部件上绘制图形就是在部件的画布上绘制。TCanvas的Brush,Pen,Font属性分别是TBrush,TPen,TFont对象,它们用于定义绘制图形的风格。
画布的笔的位置定义在PenPos属性中,可用MoveTo方法来移动笔。如果要在画布上输出文本,可用Textout方法。
Tpen Object(画笔对象)
应用程序常用TPen对象在画布上绘制各种线段,笔的颜色在Color属性中定义。线段宽度在Width属性中定义。Style属性定义了线段的各种类型,这些类型的定义与Visual C++中的定义类似。
 Mode属性定义线段的颜色。可结合当前的颜色、屏幕在Delphi中,专门定义了一组对象和部件用以绘制图形,完成一些简单的图像功能。利用这些对象、部件的方法,可以方便地绘制各种常用图形;通过设置它们的属性,能得到不同风格的图形。另外,通过对鼠标事件的定义,可以方便的设计图形绘制程序。
TCanvas Object(画布对象)
TCanvas对象是一个用于绘图的表面,在这个区域上,程序可实现各种绘图功能,很多部件(如TIMage,TMemo)的Canvas属性就是TCanvas对象。在部件上绘制图形就是在部件的画布上绘制。TCanvas的Brush,Pen,Font属性分别是TBrush,TPen,TFont对象,它们用于定义绘制图形的风格。
画布的笔的位置定义在PenPos属性中,可用MoveTo方法来移动笔。如果要在画布上输出文本,可用Textout方法。
Tpen Object(画笔对象)
应用程序常用TPen对象在画布上绘制各种线段,笔的颜色在Color属性中定义。线段宽度在Width属性中定义。Style属性定义了线段的各种类型,这些类型的定义与Visual C++中的定义类似。
 Mode属性定义线段的颜色。可结合当前的颜色、屏幕TColor类型
TColor类型用于定义一个对象颜色。很多部件的颜色属性就是TColor 类型, 在Graphics单元中TColor定义如下:
  TColor = -(COLOR_ENDCOLORS + 1)..$02FFFFF;
这是一个32位二进制数据。Graphic单元中还定义了一些常用的颜色常量,这些常量或直接映射成系统调色板中最相近的颜色,或映射成Wondows 控制面板中颜色部分的系统视频颜色。
直接映射成系统调色板中的颜色有:
ClAqua,CLBlack,ClBlue,ClbkGrray,ClFuchsoa...ClYellow
映射程序用4字节的二进制码来定义颜色,低3 位字节代表RGB 相应的颜色,如$00FF0000表示纯蓝,$0000FF00表示纯绿,$000000FF表示纯红,$00000000表示黑色,$00FFFFFF表示白色。如果最高位字节是$00,则表示用系统调色板中最相近的颜色;最高位字节是$01,则表示用当前调色板中最相近的颜色匹配;最高位字节是$02,则用当前设备描述表中逻辑调色板的次相近颜色匹配。
用Windows API的SelectPalette函数可创建逻辑调色板,要实现逻辑调色板到硬件调色板的映射,需用函数RealizePalett。
图像对象概述
TGraphic对象
TGraphic对象是TBitmap ,TIcon,Tmetafile对象的基类。如果知道图像的具体类型( 如位图, 图标元文件) , 则应将图像贮存在相应类型的对象中( 如TBitmap,TIcon,Tmetafile),否则应该使用可贮存任何图像类型的TPicture对象。
TPicture对象
TPicture对象可以保存位图、图标或元文件。Graphic属性中包括图像的类型;图像的高度和宽度分别定义在Height,Width属性中;调用LoadFromFile方法,可以从文件中装载一幅图像:
procedure TForm1.FormCreate(Sender: TObject);
begin
BitBtn1.Glyph.LoadFromFile('TARTAN.BMP');
end;
要保存一个位图,则要用SaveToFile方法;要把图像复制到剪切板,可以调用TClipboard对象的Assign方法。
TImage部件
TImage部件用以在窗体中显示图像,它的Picture 属性保存着要显示的图像, 这是一个TPicture对象。AutoSize,Stretch属性是用来调节部件与图像的大小的。当AutoSize 为真值时,TImage部件将根据它所包含的图像的大小来调整自身的大小;当AutoSize为假值时,不论图像有多大,部件将保持设计时的大小。如果部件比图像小, 那么只有一部分图像是可见的。当Stretch为真值时,位图像将根据部件的大小调整自身的大小,当部件大小改变时,元文件也做相应变化。Stretch属性对图标没有作用。
TBitmap Object(位图对象)
位图对象包含一个位图图像,有HBITMAP,HPALETE句柄,可自动管理调色板。位图对象也有画布属性。位图的Palette属性用来控制位图的颜色映射,它包括256种可显示的颜色。如果应用程序用前景色绘制位图,Palette 属性的颜色将被加入Windows系统调色板,其它颜色被映射到系统调色板已存在的颜色。如果应用程序用自己的颜色绘制位图,而其它程序已占有系统调色板,位图的颜色将被映射到系统调色板中。
如果Monochrome属性设置成假,位图将显示成彩色,反之显示成黑白色。
调用Draw和StretchDraw方法可在画布上绘制位图。

这里是一个用Canvas对象实现的小画图程序,这里看到的是程序运行的一个瞬间,源程序在例程中——Canvas文件夹中,读者可以自行参照开发应用程序。

在介绍用编程的方法来生成位图图形和进行图像处理前,我们先来看一下如何用Windows图形工具来生成和编辑位图文件。
在Windows程序生成工具软件Visual C++中,提供了生成、编辑位图图片和图标的位图编辑器。通过位图编辑器,你可以很方便的画出一张图片加入所编译的应用程序中;或是给你的程序设计一个美观的图标。你还可以将你喜欢的位图图片和图标引入到你的程序中。
你可以在程序源码中资源栏中直接新建或引入图片、图标。在已编译过的程序中你也可以用“插入”菜单中的“插入资源”选项,来完成上述这些处理操作。
使用位图编辑器,你可以很方便的生成和编辑图片和图标,但位图编辑器只能用于简单的图形处理,在下一章里我们还将为您介绍如何用程序来实现。

 

WINDOWS图像编程

  


图形设备接口(GDI,Graphics Device Interface)的主要目标之一是支持在输出设备(如视频显示器、打印机和绘图仪)上的与设备无关的图形。 GDI通过将应用程序与不同输出设备特性相隔离,使Windows应用程序能够毫无问题地在Windows支持的任何图形输出设备上运行。
Windows中的图形基本上是由从GDI.EXE模块中输出的函数处理的(尽管一些绘制函数实际上具有USER.EXE的入口点),GDI.EXE模块调用在不同驱动程序文件中的例程,其中有一个.DRV驱动程序文件用于控制显示屏幕,并且可能有一个或多个其他的.DRV驱动程序文件用来控制打印机或绘图仪。
Windows GDI使用两种坐标系统。使用虚拟坐标系统可以使程序不依赖于具体的硬件,使用设备坐标系统可以使程序和硬件紧密相联。
GDI含有在Windows应用程序内部执行、且与设备无关的图形操作函数,这些函数可产生各种各样的线、正文和位图,它们可以输出到许多不同的输出设备上。GDI允许一个应用程序产生笔、刷子、字体和位图,以供特定的输出操作使用。下面列出GDI中几组比较常用的函数:
·设备上下文函数
·椭圆和多边形函数
·绘图工具函数
·位图函数
·绘图属性函数
·正文函救
·映射函数。
·坐标函数
·元文件(metafile)函数
·区域函数
·裁剪(clipping)函数·
窗口应用程序输出图形的操作步骤如下:
①取得指定窗口的当前显示设备上下丈,显示设备上下文实际上是一个数据结构,它包括该窗口的参数及各种图形、文字属性的现行设定值,它们对以后的图形、文字输 出命令起控制作用。
②选择用户坐标系及映射方式。
③设定用户坐标系中的观察窗口和设备坐标系中的显示视区。
④输出图形、文字和图象。
⑤释放所使用的显示设备上下文。

当想要在图形输出设备(例如屏幕或打印机)上绘制图形时,必须首先获得设备上下文的句柄。先给出这个句柄,Windows才允许程序使用设备,在GDI函数中将句柄作为一个参数传入,向Windows标明需要使用的设备。
设备上下文中包含许多属性,当GDI在不同的设备上工作时都要用到这些属性。使用这些属性可使GDI只关心起始和终止坐标的大小,而不必关心有关对象的其他属性,如颜色、背景等等,因为这些都是设备上下文的一部分。当需要修改这些属性时,只需调用一个修改设备上下文中属性的参数,以后的程序中都使用修改后的设备上下文属性。设备上下文是连接Windows应用程序、设备驱动程序以及输出设备的纽带。
获取设备上下文句柄有多种方法。最一般的方法是当处理一条消息时获得了设备上下文、并在退出窗口之前释放它。一般的处理方法如下:
在处理WM_PAINT消息时
case WM_PAINT:
hdc=BeginPaint(hwnd,&ps)
EndPaint (hwnd,&ps);
其数据结构为:
HDC hWnd;
PAINTSTRUCT ps;
而在windows.h中定义了PAINTSTRUCT的数据结构。
type struct tagPAINTSTRUCT {
HDC hdC;
BOOL fErase;
RECT rcPaint;
BOOL fRestore;
BOOL flncUpdate;
BYTE rgbReserved[16];
}PAINTSTRUCT;
其中,hdc用于标识显式上下文,fErase指出背景是否重画,rcPaint是涂色矩形,其余的域均为保留。这里的hdc是BeginPaint返回的设备上下文句柄,有了从DeginPaint获取的设备上下文句柄,就可以也只能在ps指出的rcPaint的矩形内绘图,EndPaint调用使这一区域有效。
第二种方法如下所示,使用这种方法获取和释放设备上下文可以在整个用户区内画图,图形在整个用户区域内都有效:
hdC=GetDc (hwnd );
…画图操作…
ReleaseDC (hwnd , hdc );
使用下面第三种方法获取和释放设备上下文,可以在整个窗口内画图,图形在整个窗口内有效:
hdC=GetWindowDc(hwnd);
…画图操作…
ReleaseDc(hwnd,hdc);
使用下面第四种方法获取和释放设备上下文,可以在整个显示器区域内画图,图形在整个显示器区域内部有效:
hdc=CreateDC (lpszDriver ,lpszDevice ,lpszOutput , lpData);
…画图操作…
ReleaseDC(hdc);
其中lpszDriver指向设备驱动程序的DOS文件名(不带扩展名),lpszDevice指向专用设备名(例如Epson Fx-80),lpszOutput指向物理输出介质(文件或输出端口)的DOS文件名或设备名,lpData指向含有设备驱动程序的设备专用的初始化数据的DEVMODE数据结构。例如:
hdc=CreateDC("DISPLAY",NULL,NULL,NULL);
使用屏幕画图,而:
hdc= CreateDC ("IBMGRX","IBM Graphics","LPT1",NULL );
在打印机上输出图形,这里的lpData置为默认值,可以在WIN.INI中找到初始化值。
如果不需要获取设备上下文,即不需要在设备上下文中操作,只需了解有关设备上下文的信息,可以用如下语句:
hdcInfo = CreateDC (lpszDriver, lpszDevice,lpszOutput, lpData );
……
DeteteDC (hdcInfo);
另外,还可以使用设备上下文来对位图的内存进行控制,如下所示:
hdcMem = CreateCompatibleDC (hdc)
OeleteDc(hdcMem );
一个元文件是以二进制形式编码的GDI调用集合,可通过获取一个元文件设备上下文来建立一个文件:
hdcMeta=CreateMetaFile(lpszFilename);
……
hmf=CloseMetaFile(hdCMeta);
在元文件设备上下文有效期间,使用hdcMeta所进行的任何GDI调用都成为元文件的一部分,当调用CloseMetaFile时,设备上下文句柄变化无效,函数返回元文件(hmf)的句柄。
一个设备上下文通常涉及物理设备,如视频显示器、打印机等,所以需要获取有关该设备的信息,如显示器大小和彩色能力等。可以通过调用GetDeviceCaps函数来获取这样的信息:
nValue=GetDeviceCaps (hdc,nIndex);
这里的hdc标识设备上下文,nIndex确定返回值,它可以是window.h中所定义的28个标识符中的一个,例如nIndex=DRIVEVERSION,则该函数返回的是版本号。

真正影响在用户区域上绘制过程的设备上下文属性是“映射方式”,与映射方式属性密切相关的还有如下四个设备上下义属性:窗口原点、视窗原点、窗口范围和视窗范围。
Windows定义了八种映射方式,即:













这里的TWIP指的是1/1440英寸,in.代表英寸。
可以调用函数setMapMode(hdc,MapMode)来设置这八种映射方式中的一种。hdc用来标识设备上下文,nMapMode可以取MM_TEXT、MM_LOMETRIC、MM_HIMETRIC等八个值中的一个。在设置了映射方式之后,到下一次设置映射方式之前,Windows一直使用这种映射方式。如果想要获取当前的映射方式,可用:
nMapMode= GetMapMode (hdc)
在设置了映射方式之后,就规定了逻辑单位的大小和增量的方式,在GDI画图函数中,可以不必考虑这些内容而直接使用逻辑数字,如:
SetMapMode(hdc ,MM_TEXT);
TextOut(hdc,8 ,16,szBuffer ,nLength)
即正文从用户区域左起第八个象素,顶边起第16个象素的位置开始写操作。不管映射方式如何,Windows函数中所有坐标规定为-32768 到 32767之间的带符号短整救。
注意映射方式只是一个设备上下文属性,因此映射方式唯一起作用的是将映射方式作为设备上下文句柄属性,而将该句柄当作参数的GDI函数,因此象GetSystemMetrics这样的非GDI函数,将继续以设备单位(象素值)返回尺寸值。

用GDI的SetPixel函数可以绘制一特定颜色的象素:
rgbActualColor =SetPixel (hdc,x,y,rgbColor);
这里hdc标识设备上下文,x ,y表示点坐标,rgbColor为一无符号的长整数,其结构为:
COLORREF rgbColor; 
其中低位字节为红基色的相对亮度值,第二个字节包含绿基色的相对亮度值,第三个字节包含蓝基色的相对亮度值,高位字节必须为零。可以使用RGB函数来获取rgbColor。
rgbColor =RGB(byRed ,byGreen,byBlue);
这里的byRed、byGreen、byBlue取值范围为0~255,分别代表红色、绿色、蓝色的亮度。给出正确的参数之后,SetPixel返回的是调色板中最靠近所需彩色的颜色。还可以使用如下方法来取得一个特定象素的颜色:
rgbCotor= GetPixel(hdc,x,y);

画线函数主要有三种, LineTo、Polyline 和 Arc。还有五个设备上下文属性会影响这些函数画出的线的外观:笔的当前位置(仅对LineTo有影响)、笔、背景方式(对非实心笔有影响)、背景颜色(对 OPAQUE背景方式)以及绘制方式。
在这些设备上下文的属性中,笔的当前位置影响画线的起点,笔影响线的粗细等形状,背景方式影响非实心笔画出的线的模板图形,背景颜色影响线模板背景色,绘制方式影响实心线、虚线等线属性。
以下是典型的画线操作步骤:
MoveTo(hdc,xStart,yStart);
LineTo(hdc ,xEnd ,yEnd);
上面两句画出一条从(xStart,yStart)到(xEnd,yEnd)的直线。 
可以使用语句:
dwPoint = GetCurrentPosition (hdc);
获得笔的当前位置。这里,dwPoint返回值是一个无符号长整数(或双倍长字),其中低位字含有X坐标,高位字含有Y坐标。
可以使用MAKEPOINT函数将dwPoint转换为POINT结构;
point = MAKEPOINT (dwPoint);
point的类型为POINT: 
typedef struct togPOINT {kk1} 
int x;
int y;
}POINT;
Polyline用于绘制折线,例:
Polyline(hdc,&pt,5)
将数组pt中的5个点之间用线段相连。
Arc用于画椭圆的周边:
Arc (hdc,xLeft,yTop,xRight,yBottom,xStart,yStart,XEnd,yEnd );
画出的椭圆以左上角为(xLeft,yTop),右下角为(xRight,yBottom)的矩形为界,圆弧开始于椭圆和(xStart,yStart)与椭圆中心的连线的交点处,沿着椭圆周边的过时针方向绘制,并终止于椭圆和(xEnd,yEnd)与椭圆中小的连线的交点处。
当调用LineTo、Polyline和Arc时,Windows使用当前在设备上下文中选择的笔来画线,笔决定了线的颜色、密度和型式,而线型可以是实线、点线或短划(虚)线,缺省设备上下文中的笔叫做BLACK_PEN,不管映射方式如何选支笔以一个象素的宽度画黑色的实线, BLACK_PEN是Windows提供的三支“备用笔”之一,其他两支是WHITE_PEN和NULL_PEN,NULL_PEN是一支什么都不画的空笔,当然用户也可以自己建立定制的笔。
可以通过一个句柄来引用所需的笔: 
HPEN hPen;
hPen =GetStockObject(WHITE_PEN);
SelectObjeCt (hdc ,hPen) ;
调用GetStockObject获得一支备用笔(WHITE_PEN)的句柄,调用SelectObject使这支笔成为设备上下文中当前选择的用CreatePen或CreatePenIndirect函数建立一支“逻辑笔”,这逻辑笔只是一支笔的描述。
hPen = CreatePen (nPenStyle ,nWidth ,rgbColor );
其中nPenStyle参数确定笔是绘制实线还是由点或短划组成的线。该参数可取下列标识符之一: PS_SOLID、PS_DASH、Ps_DOT、PS_DASHDOT、 PS_DASHDOT DOT。nWidth表示笔宽(采用逻辑单位),rgbColor表示笔的颜色,如果函数执行成功,则返回值标识一支逻辑笔,否则返回值为NULL。
另外还有一种方法来建立逻辑笔:
typedef struct tagLOGPEN { kk1}
POINT lopnWidth; 
COLORREF lopnColor;
}LOGPEN;
以上是Windows. h中对LOGPEN结构的定义。lopnStyle规定笔的型式,lopnWidth规定笔的宽度,lopnColor规定笔的颜色。然后将logpen定义为LOGPEN类型:
LOGPEN logpen;
再使用
hPen = CreatePenlndirect (&logpen);
就可以建立自定义的笔了。
因为用以上两种方式建立的笔不是设备上下文有关的,所以可以同时建立多支笔,并用多支笔画图。
hpenl =CreatePen(PS_SOLID,1,0L); 
hpen2=CreatePen(PS_SOLID,3,RGB(255,0,255)),
selectObject (hdc ,hpenl )
//LineTo调用
SelectObject (hdc , hpen2 )
//Polyline调用
DeleteObject(hpenl);
DeleteObject(hpen2);
在点线笔和短划笔的空隙间的着色取决于设备上下文中定义的背景方式和背景颜色这两种属性。缺省的背景方式是 OPAQUE ,即用背景颜色来填充空隙,缺省的背景颜色是白色。可用:
SetBkColor (hdc,rgbColor );
来改变填充空隙的背景颜色。可用:
SetBkMode(hdc,mode);
来改变背景方式,这里的mode可为OPAQUE或TRANSPARENT。
在设备上下文中可设置口种新的绘制方式。nDrawMode参数定义绘制方式。可用下列函数获得当前绘制方式:
nDrowMode = GetRop2 (hdc);
使用这种绘制方式实际上是在笔的象素和目标显示表面的象素之间执行一种逐位布尔运算。

下面列出了Windows的六个函数,用于绘制带有边框的填充区域:
Rectangle, 带有方形角的矩形
Ellipse, 椭圆
RoundRect, 带有圆形角的矩形
Chord, 在椭圆周边上的弧,其断 . 点用一条弦相连接
Pie, 在椭圆周边上的饼形楔
Polygon, 多边形
PolyPolygon, 多个多边形
图形要用设备上下文中选择的当前刷子进行填充, 例:
HBRUSH hBrush;
hBrush=GetStockObject(GRAY_BRUSH)
SelectObject (hdc,hBrush)
对于矩形填充对象,使用:
Rectangle(hdc,xLeft,yTop,xRight,yBottom);
点(xLeft,yTop)是矩形的左上角,而(xRight,yBottom)是右下角(这两点均以逻辑坐标给出)。同时还要考虑这两点坐标的合理性,例如,在MM_TEXT映射方式下,xRight必须大于xLeft,yBottom必须大于yTop。
对于椭圆填充对象,使用:
Ellipse(hdc,xLeft,yTop,xRight,yBottom);
画圆角矩形的填充对象,使用:
RoundRect (hdc,xLeft,yTop,xRight,yBottom,xCornerEllipse,yCornerEllipse);
rgbColor为指定刷子的前景颜色,即阴影线的颜色。
③第三种方法:
hBrush = CreatePatternBrush ( hBitmap ) ;
本函数建立一把具有由hBitmap参数指定图案的逻辑刷子,该刷子以后可被任何支持光栅操作的设备所选用。hBitmap标识位图,该位图可用函数CreateBitmap、CreateBitmapIndirect、LoadBitmap或CreateCompatibleBitmap创建。用于填充图案的位图的最小尺寸为8×8。
④第四种方法:
hBrush = CreateBrushIndirect ( &logbrush )
变量logbrush为一个LOGBRUSH(“逻辑刷”)类型的结构。建立了逻辑刷子之后,可以使用:
SelectObject (hdc , hBrush ) ;
将逻辑刷送入设备上下文中。如果使用结束,可以用:
DeletObject (hBrush ) ;
删除一把已建立的刷子,如果在程序中需要获取有关于刷子的信息,则可以调用:
GetObject (hBrush , sizeof (LOGBRUSH ) , (LPSTR ) & (logBrush ) ) ;
其中logbrush为一个LOGBRUSH类型的结构。
前面提到使用位图作为逻辑刷子的图案,这样就需要一个位图的句柄。位图必须至少是8象素高和8象素宽,如果位图太大,Windows就取位图的左上部分作为刷子。获取位图句柄也有四种方法。
①获取位图句柄的第一种方法是:
首先用Windows提供的SDKPAINT生成一个位图文件(扩展名为.BMP),并把文件名包括在资源文件(.rc文件)中的一个BITMAP语句中,如:
lpszBitmap BITMAP " BitmOPName.Bmp"
(假设SDKPAINT中产生的位图名为BitmapName.Bmp),然后将位图装入:
hBitmap = LoadBitmap (hInstance , lpszBitmap ) ;
这里hBitmap即为位图句柄。
②第二种获取位图句柄的方法是:
hBitmap= createBitmop(nWidth,nHeight ,nPlanes ,nBitsPixel,lpBits);
本函数可生成一个具有指定的宽度、高度和位图案(bit pattern)并与设备有关的内存位图。其中的参数nWidth指定位图的宽度(以象素为单位),nHeight指定位图的高度(也以象素为单位),nPlanes指定位图中的彩色位平面的个数,每个彩色位平面有nWidthXnHeight XnBitsPixel位。
nBitsPixel指定每个显示象素的颜色位数。lpBits指向一个含有初始位图位值的短整型数组,它的值与BITMAP结构中的bmBits值相类似,有关BITMAP结构的内容已在前面
的章节中提到。
③第三种获取位图句柄的方法:
hBitmap= CreateCompatibleBitmap (hdc,nWidth,nHeight);
本函数生成一个与由hdc参数指定的设备相兼容的位图,此位图具有与其他设备相同数值的彩色位平面或相同的每象素位数的格式。它的彩色信息同样由hdc指定的设备所确定。hdc标识设备上下文,nWidth和nHeight分别指定位图的宽和高(以位数表示)。
④第四种获取位图句柄的方法:
hBitmop = CreateBitmapIndirect (&bitmap):
本函数生成一个具有由lpBitmap参数所指定的数据结构给出的宽度、高度和位图图案的位图。这里的bitmap的结构为BITMAP类型。
当拥有一个位图句柄之后,还可以调用:
GetObject(hBitmap,sizeof(BITMAP),(LPSTR)&bitmap);
来获得有关位图的信息,这里返回的bitmap为一个BITMAP类型的结构。
还可以调用:
GetBitmapBits (hBitmap,dwCount,lpBits);
将位图hBitmap的dwCount个字节复制到地址为lpBits的数组中。同样还可以使用
SetBitmopBits (hBitmap,dwCount,lpBits);
将 lpBits 标识的彩色位值放置到 hBitmap 标识的位图中去。
当Windows用刷子填充一个区域时,它在水平和垂直两个方向上重复地使用8×8位图,刷子的外观可能会有细微的变化,这取决于Windows如何将位图的左上角与显示表面对齐,设备上下文中将这种对齐的属性称为“刷子原点”。
大多数情况下,绘图时不必如此精细地调整刷子原点;但在某种情况下,为了产生某种特定的视觉效果,需要这么做。可以通过下面步骤来达到这个效果:
①对刷子调用UnrealizeObject(但不能对备用刷子调用UnrealizeObject);
UnrealizeObject(hObject );
此函数表示GDI在下次给定的刷子被选中时,使它的原点复位。
②用SetBrushOrg设置刷子原点:
BrushOrg=SetBrushOrg(hDc,x ,y);。
此函数用于设置当前选人给定的设备上下文中的刷子的原点。其中,hDc标识设备上下文,x规定新原点的x 坐标(采用设备坐标),该值范围必须在0~7之间,y规定新原点的y坐标(采用设备坐标),该值范围必须在0~7之间,该函数返回一个双字,先前的x坐标放在低位字,先前的y坐标放在高位字。

除了画点、画线、区域填充这些绘图函数之外,Windows还包括几个附加的绘图函数,用以处理RECT(矩形)结构和“区域”,区域是屏幕的一个由矩形、其他多边形和椭圆组合而成的部分。
FillRect(hdc ,&rect,hBrush);
此函数是用选定的刷子填充给定的矩形,FillRect函数填充整个矩形,还包括左边线和顶边线,但不包括右边线和底边线。hdc标识设备上下文,rect为RECT数据结构类型,此数据结构存放要填充的矩形的逻辑坐标。hBrush用来标识填充此矩形的刷子。
FrameRect (hdc ,&rect);
本函数在 lpRect 参数指定的矩形周围画出一个边框,FrameRect 函数采用给定的刷子绘制边框,边框的高和宽总是用逻辑单位表示。hdc标识窗口的设备上下文, rect 标明左上角和右下角的逻辑坐标,hBrush标识画矩形边框用的刷子。
InvertRect (hdc,&rect);
该函数反视频显示给定矩形的内容。
前面提到的rect可用以下语句取得:
SetRect(&rect,xLeft,yTop,xRight,yBottom);
区域用以描述显示器上的一个由矩形、其他多边形和椭圆组合而成的部分、可以用区域进行绘制或裁剪。
可用:
hRgn=CreateRgn(xLeft,yTop,xRight,yBottom);

hRgn = CreateRectRgnIndirect (&rect );
来建立矩形区域,也可以用:
hRgn= CreatEllipseRgn (xLeft,yTop,xRight,yBOttom);
或:
hRgn =CreateEllipseRgnIndirect (&rect);
来建立椭圆形区域;还可以用:
hRgn=CreatePolygonRgn (Upoint,nCount,nPolyFillMode);
来建立多边形区域,用:
hRgn=CreateRoundRectRgn(xl,yl,x2,y2,x3,y3);
来建立一个带有圆角的矩形区域,(xl,y1)表示区域左上角,(x2,y2)表示区域右下角,x3和y3分别用于指定产生圆角的椭圆的宽度和高度。
各个特定区域的句柄产生之后,
还可以使用:
nRgnType= CombineRgn (hDestRgn,hSrcRgnl,hSrcRgn2,nCombine);
通过组合两个已存在区域而产生一个新的区域。组合区域的方法由nCombine参数加以说明:
这里的hSrcRgnl和hSrcRgn2标识两个已存在的区域,hDestRgn标识将被新区域替换的作为目标的已存在区域。
一个区域用完后,可用DeleteObject(hRgn)删除它。

FloodFiLL (hdc,xStart,yStart,rgbColor);
本函数用当前刷子填充显示表面的一个区域,该区域假定由rgbColor多数指定的颜色边界确定,FloodFill函数由xStart,yStart参数指定的,点开始向各个方向进行填充,直到颜色边界为止。
ExtFLoodFill(hdc,xStart,yStart,rgbColor,wFill );
此函数用当前刷子填充显示表面上的某一区域,若wFill被设置成FLOODFILLBORDER,此区域将完全用rgbColor设定的颜色作为边界,ExtFloodFill从(xStart,yStart)点开始向各个方向填充,直到该颜色边界为止。若wFill被设置成FLOODFILLSURFACE,则ExtFLOODFILL函数从(xStart,yStart)点开始,向各个方向对所有包含由rgbColor参数设定的颜色的相邻区域进行填充。
DrowIcon(hdc,xStort,yStart,hIcon) ;
本函数在指定设备上画一个图标。(xStart,yStart)为图标的左上角,hIcon标识要画的图标,它与hBitmap一样,可以由SDKPAINT画出,在. rc文件中定义,用LoadIcon获得。
ScrollWindow (hwnd,xScroll,yScroll,&rectScroll,&rectClip) ;
本函数通过移动一个窗口用户区域的内容来滚动该窗口,沿X轴移动xScroll个单位,沿Y轴移动yScroll个单位,rectScroll表示用户区域要滚动的那一部分内容,rectClip表示要滚动的裁剪矩形。
ScrollDC(hdc,dx,dy,lprcScroll,lprcClip, hrgnUpdate,lprcUpdate); 
此函数水平地和垂直地滚动由位构成的矩形。lprcScroll指向要滚动的矩形,dx表示水平滚动单位,dy表示垂直滚动单位,lprcClip指向裁剪矩形,hrgnUpdate返回滚动过程没有覆盖的区域,lprcUpdate返回需要重画的最大矩形区域。

OpenGL是近几年发展起来的一个性能卓越的三维图形标准,它是在SGI等多家 世界闻名的计算机公司的倡导下,以SGI的GL三维图形库为基础制定的一个通用共享的开放式三维图形标准。目前,包括Microsoft、SGI、IBM、DEC、SUN、HP等大公司都采用了OpenGL做为三维图形标准,许多软件厂商也纷纷以OpenGL 为基础开发出自己的产品,其中比较著名的产品包括动画制作软件Soft Image 和3D Studio MAX、仿真软件Open Inventor、VR软件World Tool Kit、CAM软件ProEngineer、GIS软ARC/INFO等等。值得一提的是,随着Microsoft公司在Windows NT和最新的Windows 95中提供了OpenGL标准及OpenGL三维图形加速卡(如北京黎明电子技术公司的AGC-3D系列三维图形加速卡)的推出,OpenGL将在微机中有广泛地应用,同时也为广大用户提供了在微机上使用以前只能在高性能图形工作站上运行的各种软件的机会。
OpenGL实际上是一个开放的三维图形软件包,它独立于窗口系统和操作系统,以它为基础开发的应用程序可以十分方便地在各种平台间移植;OpenGL可以与Visual C++紧密接口,便于实现机械手的有关计算和图形算法,可保证算法的正确性和可靠性。
OpenGL使用简便,效率高。它具有七大功能: 
1) 建模 OpenGL图形库除了提供基本的点、线、多边形的绘制函数外,还提供了复杂的三维物体(球、锥、多面体、茶壶等)以及复杂曲线和曲面 (如Bezier、Nurbs等曲线或曲面)绘制函数。 
2) 变换 OpenGL图形库的变换包括基本变换和投影变换。基本变换有平移、旋转、变比镜像四种变换,投影变换有平行投影(又称正射投影)和透视投影两种变换。其变换方法与机器人运动学中的坐标变换方法完全一致,有利于减少算法的运行时间,提高三维图形的显示速度。 
3) 颜色模式设置 OpenGL颜色模式有两种,即RGBA模式和颜色索引(Color Index)。 
4) 光照和材质设置OpenGL光有辐射光(Emitted Light)、环境光(Ambient Light)、漫反射光(Diffuse Light)和镜面光(Specular Light)。 
材质是用光反射率来表示。场景(Scene)中物体最终反映到人眼的颜色是光的红绿蓝分量与材质红绿蓝分量的反射率相乘后形成的颜色。 
5) 纹理映射(Texture Mapping) 利用OpenGL纹理映射功能可以十分逼真地表达物体表面细节。 
6) 位图显示和图象增强 图象功能除了基本的拷贝和像素读写外,还提供融合(Blending)、反走样(Antialiasing)和雾(fog)的特殊图象效果处理。 
以上这三条可使被仿真物更具真实感,增强图形显示的效果。 
7) 双缓存(Double Buffering)动画 双缓存即前台缓存和后台缓存,简而言之,后台缓存计算场景、生成画面,前台缓存显示后台缓存已画好的画面。 
此外,利用OpenGL还能实现深度暗示(Depth Cue)、运动模糊(Motion Blur)等特殊效果。从而实现了消隐算法。

OpenGL图形库一共有100多个函数。其中核心函数有115个,它们是最基本的函数,其前缀是gl,OpenGL实用库(OpenGL utility library , GLU)的函数功能更高一些,如绘制复杂的曲线曲面、高级坐标变换、多边形分割等,共有43个,前缀为glu;OpenGL辅助库(OpenGL auxiliary library ,GLAUX)的函数是一些特殊的函数,包括简单的窗口管理、输入事件处理、某些复杂三维物体绘制等函数,共有31个,前缀为aux。 
此外,还有六个WGL函数非常重要,专门用于OpenGL和Windows 95窗口系统的联接,其前缀为wgl,主要用于创建和选择图形操作描述表(rendering contexts)以及在窗口内任一位置显示字符位图。这些功能是Windows 95 对OpenGL的唯一补充。 
另外,还有五个Win32函数用来处理像素格式(pixel formats)和双缓存。由于它们是对Win32系统的扩展,因此不能应用在其它OpenGL平台上。

OpenGL for Windows 95的设计与OpenGL for UNIX的程序设计有一点小区别,关键就在于如何将OpenGL与不同的操作系统下的窗口系统联系起来。 
如果调用OpenGL辅助库窗口管理函数,则不用考虑这些问题。下面简要介绍在 Windows 95下 OpenGL 的程序设计关键。 
1.图形操作描述 
在Windows 95下窗口程序必须首先处理设备描述表(Device Contexts ,DC),DC包括许多如何在窗口上显示图形的信息,既指定画笔和刷子的颜色,设置绘图模式、调色板、映射模式以及其它图形属性。同样,OpenGL for Windows 95的程序也必须使用DC,这与其它Windows 95程序类似。但是,OpenGL for Windows 95必须处理特殊的DC图形操作描述表,这是DC中专为OpenGL使用的一种。一个OpenGL应用图形操作描述表内有OpenGL与Windows 95窗口系统相关的各种信息。一个OpenGL应用首先必须创建一个图形操作描述表,然后再启动它,最后在所定义的窗口内按常规方式调用OpenGL函数绘制图形。 
一个图形操作描述表不同于其它DC,它们调用每个GDI函数都需要一个句柄,而图形操作描述表方式下只需一个句柄就可以任意调用OpenGL函数。也就是说,只要当前启用了某个图形操作描述表,那么在未删除图形操作描述表之前可以调用任何OpenGL函数,进行各种操作。
2.像素格式 
在创建一个图形操作表之前,首先必须设置像素格式。像素格式含有设备绘图界面的属性,这些属性包括绘图界面是用RGBA模式还是颜色表模式,像素缓存是用单缓存还是双缓存,以及颜色位数、深度缓存和模板缓存所用的位数,还有其它一些属性信息。 
3.像素格式结构 
每个OpenGL显示设备都支持一种指定的像素格式。一般用一个名为PIXELFORMATDESCRIPTOR的结构来表示某个特殊的像素格式,这个结构包含26个属性信息。Win32定义PIXELFORMATDESCRIPTOR如下所示: 
typedef struct tagPIXELFORMATDESCRIPTOR 
{ kk1}
// pfd 
WORD nSize; 
WORD nVersion; 
DWORD dwFlags; 
BYTE iPixelType; 
BYTE cColorBits; 
BYTE cRedBits; 
BYTE cRedShift; 
BYTE cGreenBits; 
BYTE cGreenShift; 
BYTE cBlueBits; 
BYTE cBlueShift; 
BYTE cAlphaBits; 
BYTE cAlphaShift; 
BYTE cAccumBits; 
BYTE cAccumRedBits; 
BYTE cAccumGreenBits; 
BYTE cAccumBlueBits; 
BYTE cAccumAlphaBits; 
BYTE cDepthBits; 
BYTE cStencilBits; 
BYTE cAuxBuffers; 
BYTE iLayerType; 
BYTE bReserved; 
DWORD dwLayerMask; 
DWORD dwVisibleMask; 
DWORD dwDamageMask; 
} PIXELFORMATDESCRIPTOR;

4.初始化PIXELFORMATDESCRIPTOR结构 
PIXELFORMATDESCRIPTOR中每个变量值的具体含义和设置可以参考有关资料,下面举出一个PIXELFORMATDESCRIPTOR初始化例子来简要说明相关变量的意义。定义PIXELFORMATDESCRIPTOR结构的pfd如下: 
PIXELFORMATDESCRIPTOR pfd = { kk1}
sizeof(PIXELFORMATDESCRIPTOR), . //size of this pfd 1
PFD_DRAW_TO_WINDOW| // support window 
PFD_SUPPORT_OPENGL| // support OpenGL 
PFD_DOUBLEBUFFER, // double buffered 
PFD_TYPE_RGBA, // RGBA type 
24, // 24-bit color depth 
0,0,0,0,0,0, // color bits ignored 
0, // no alpha buffer 
0, // shift bit ignored 
0, // no accumulation buff 
0,0,0,0, // accum bits ignored 
32, // 32-bit z-buffer 
0, // no stencil buffer 
0, // no auxiliary buffer 
PFD_MAIN_PLANE, // main layer 
0, // reserved 
0,0,0 // layer masks ignored 
}; 
在这个结构里,前两个变量的含义十分明显。第三个变量dwFlags的值是
PFD_DRAW_TO_WINDOW |PFD_SUPPORT_OPENGL ,
表明应用程序使用OpenGL函数来绘制窗口
第四个:
PFD_DOUBLEBUFFER,
表明当前采用RGBA颜色模式,第五个采用24位真彩色,既1.67千万种颜色,如果是256色系统则自动实现颜色抖动;因为没有使用alpha缓存和累计缓存,所以从变量cAlphaBits到cAccumAlphaBits都设置为0;深度缓存设置为32位,这个缓存能解决三维场景的消隐问题;变量cAuxBuffers设置为0,在Windows 95下不支持辅助缓存;Windows 95下针对OpenGL变量ilayerType只能设置为PFD_MAIN_PLANE,但在其它平台也许支持PFD_MAIN_PLANE或PFD_MAIN_UNDERLAYPLANE;接下来bReserved变量只能设为0,而最后三个变量Windows 95都不支持,故全设置为0。 
5.设置像素结构 
当初始化PIXELFORMATDESCRIPTOR结构后,就要设置像素格式。下面举例说明如何设置像素格式。 
CClientDC clientDC(this); 
int PixelFormat = ChoosePixelFormat(clientDC.m_hDC,&pfd); 
BOOL result=SetPixelFormat(clientDC.m_hDC,PixelFormat,&pfd); 
第一行语句说明如何得到一个应用窗口客户区的设置描述表。 
第一行调用ChoosePixelFormat( )选择一个像素格式,并将像素格式索引号返回给pixelFormat变量;函数中第一个参数是选择像素格式的设备描述表的句柄,第二个参数是PIXELFORMATDESCRIPTOR结构的地址。如果调用失败则返回0;否则返回像素格式索引号。 
第三行调用SetPixelFormat( )设置像素格式,三个参数分别是设备描述表的句柄、像素格式索引号和PIXELFORMATDESCRIPTOR结构的地址。如果调用成功则返回TRUE,否则返回FALSE。 
6.创建图形操作描述表 
正如前所述,必须创建图形操作描述表并启用它后,才能调用OpenGL函数在窗口内进行各种图形操作。一般来说,利用 MFC 中增补的管理图形操作描述表方法来编程比较方便。即在视类 (CView) 的消息 OnCreat( ) 中创建图形操作描述表。
                                                     

     动画技术

  


所谓动画也就使一幅图像“活”起来的过程。使用动画可以清楚的表现出一个事件的过程,或是展现一个活灵活现的画面。如今电脑动画的应用十分广泛,无论是让应用程序更加生动,增添多媒体的感官效果;还应用于游戏的开发,电视动画制作,创作吸引人的广告,电影特技制作,生产过程及科研的模拟等等。
从某种极端的意义上说,产生动画的最好方法并不是编程,而是……放电影。大概你还记得童年时在课本每页右下角画上做不同连续动作的小人再快速翻阅产生的“原始动画”。我们在常识课上就知道利用人类视觉暂留的原理快速连续显示一系列静止画面可以产生动画。每秒播映24张胶片的电影和每秒显示30帧画面的电视(NTSC制)足以蒙骗我们可怜、不灵敏的眼睛。计算机动画(Computer Animation)也不例外,应用程序以一定的规律快速连续绘制并显示一系列有关联的静止图像就产生了电脑动画。
根据计算机动画绘制和显示的原理,根据动画的交互程度我们可以将计算机动画分为非交互动画和交互动画两大类。而交互动画又可进一步被区分为非即时计算交互动画和即时计算交互动画两种。
在本章里,我们将简单介绍一些动画制作的基本概念,并结合几个简单的程序来说明如何荣编程的方法来实现动画过程。

动画过程的实现有很多种方法,下面我们简单介绍其中的几种:
子图形动画 
子图形(sprite)在交互式电子游戏中是用来表示各个角色以及不动的背景。当游戏者移动输入设备如游戏杆、跟踪球或者鼠标时,子图形就在背景上移动。玩电子游戏的游戏者实际上是在操作子图形。Uideo游戏机通常都带有图形硬件,内部支持子图形。而IBM兼容pc机内的显示硬设备并不支持子图形,所以必须依靠软件技术实现。
擦除和重画技术
在旧位置擦除图像并在新位置重新绘出,这显然是移动图像的一种方法,在Windows程序中,可以通过调用BitBlt函数实现这个方法,如果反复不断地擦除和重绘,图像看起来就会像是在屏幕上穿越移动。但是,这种方法的主要缺点是在擦除和重绘图像时会发生显示闪烁现象。
有一个办法可以避免擦除-重绘动画时产生的闪烁现象,那就是采用视频页切换,只要显示硬设备支持一个以上的视频页。使用多个视频页,就可以在显示活动页的同时在隐藏的视频页上绘制整个屏幕以显示更新后的图形。要继续显示动画,只需用一个循环重复这一过程。许多高级图形工作站(例如, Silicon Graphics 工作站) 支持通过页的切换来实现动画——用工作站界的行话来说是“缓冲区交换”技术。
不巧的是,绝大多数PC显示适配器在高分辨率视频方式下并不支持多视频页。更重要的是,Microsoft Windows也不支持多视频页。所以必须想其他办法在Windows中创建不闪烁的动画。

擦除和重绘动画伴随着屏幕闪烁,其原因是屏幕绘制操作都是可见的,当图像被擦除,它就从屏幕上消失了,然后这个图像又出现在一个新的位置上。如果使用两个视频页,闪烁现象将不复存在,因为屏幕的更新是在隐藏页上完成的,视频页交换的同时就显现整个已更新的屏幕。按照这个逻辑,只要图像准备好,并且更新的屏幕能迅速重绘,就可以避免闪烁。
好在Windows支持在离屏位图上绘图,这样离屏位图就可以作为准备显示屏幕的理想的“画布”,简单调用BitBlt函数,更新后的图像就会迅速地传输到显示屏幕上,当然,还得注意众多细节,以能够在离屏位图上正确地绘制图像,不过这个基本思想倒是很适用于在Windows下的图像动画。

调用SetBackground函数,其参数是一个指向新的被用作背景图像的DIB指针。这个调用的结果是文档拥有这个DIB,当文档被破坏或背景DIB被新的替代时,此DIB将被消除。显然,改变背景图像将涉及到一些重画操作,但稍后用户才能碰到这些问题,以下是SetBackground函数的程序代码:
BOOL CAnimDoc::SetBackground(CDIB* pDIB)
{kk1}
// Delete any existing background DIB and set the new one.
if (m_pBkgndDIB) delete m_pBkgndDIB;
m_pBkgndDIB = pDIB;

// Note that the document has changed.
SetModifiedFlag(TRUE);

// Tell the view that it needs to create a new buffer
// and palette.
CAnimView* pView = GetAnimView( );
ASSERT(pView);
return pView->NewBackground(m_pBkgndDIB);
}
清除现存的背景图像,并用新的背景代替。注意:传送空指针给SetBackground将导致那根本没有背景图像,然后文档也标明已发生改变。
我们再来看一下CanimView::NEW Background函数上来,首先,添加一个装入新背景图像的菜单选项。用App Studio在File菜单中添加一个Load Background选项,然后在CanimDoc类中用ClassWizard为该命令建立一个处理函数。一下是处理程序代码:
void CAnimDoc::OnFileLoadBkgnd()
{kk1}
// Create a DIB to hold the image.
CDIB* pDIB = new CDIB;
// Show the file open dialog for a DIB.
if (!pDIB->Load()) {kk1}
delete pDIB;
return;
}
// Make sure this is an 8-bpp DIB
BITMAPINFO* pBMI = pDIB->GetBitmapInfoAddress();
ASSERT(pBMI);
if (pBMI->bmiHeader.biBitCount != 8) {kk1}
AfxMessageBox("Only 8-bpp DIBs are supported");
delete pDIB;
return;
}
// Replace any existing background DIB with the new one.
if (!SetBackground(pDIB)) {kk1}
delete pDIB;
}
}
OnFileLoadBkgnd使用CDIB类中的新Load函数显示一个允许用户选择并装入背景图像DIB的对话框。如果成功,则SetBackground函数把新装入的DIB用作当前的背景图像。对于CDIB::Load函数,它带有一个文件名参数活不带参数,读者可以参照例程。
现在已有装入新背景图像DIB的菜单选项及将该图像附给文档的程序代码。下面我们只需作两件事:
传建一个新的屏幕缓冲区
创建一个新的待用体调色板,把创建的屏幕缓冲区画到视图窗口的设备上下文(DC)。还可以调整父画面的大小从而使视图窗口正好适合背景图像。
这里我们看一下获取视图指针后,如何调用视图的公共成员函数。以下是CAnimView::NewBackground的功能:
BOOL CAnimView::NewBackground(CDIB* pDIB)
{kk1}
// Create a new buffer and palette.
if (!Create(pDIB)) {kk1}
return FALSE;
}

// Map the colors of the background DIB to
// the identity palette we just created for the background.
pDIB->MapColorsToPalette(GetPalette());

// Resize the main frame window to fit the background image.
GetParentFrame()->RecalcLayout();
ResizeParentToFit(FALSE);
// Try shrinking first.
ResizeParentToFit(TRUE); 
// Let's be daring.
// Render the entire scene to the off-screen buffer.
Render();

// Paint the off-screen buffer to the window.
Draw();

return TRUE;
}
重画视图
将背景DIB画到缓冲区并更新屏幕到视图窗口。这时构图(Render)函数将背景DIB拷贝到屏幕缓冲区,画图(Draw)函数把屏幕缓冲区画到视图窗口。画图(Draw)实在COSBView基类中实现的,读者可参见例程。下面是构图(Render)函数:
void CAnimView::Render(CRect* pClipRect)
{kk1}
CAnimDoc* pDoc = GetDocument();
CRect rcDraw;

// Get the background DIB and render it.
CDIB* pDIB = pDoc->GetBackground();
if (pDIB) {kk1}
pDIB->GetRect(&rcDraw);
// If a clipping rectangle was supplied, use it.
if (pClipRect) {kk1}
rcDraw.IntersectRect(pClipRect, &rcDraw);
}
// Draw the image of the DIB to the off-screen buffer.
ASSERT(m_pDIB);
pDIB->CopyBits(m_pDIB, 
rcDraw.left,
rcDraw.top, 
rcDraw.right - rcDraw.left,
rcDraw.bottom - rcDraw.top,
rcDraw.left, 
rcDraw.top);
以上就是创建背景图像的基本过程。

在Windows应用程序中,如果想使用离屏位图制作子图形动画,就需要C++类来表示子图形和便于图形动起来。动画包含一个固定的背景图像,零个或多个能在背景上四处移动的子图形。下面各节定义了 Sprite 类用以构造子图形的模型,还定义了 SpriteAnimation 类来维护子图形和背景图像。
Sprite类
Sprite有两个Image对象:
·黑色背景上的Sprite图像
·白色背景上的Sprite图像的黑色轮廓像(掩膜)
animate 函数是 SpriteAnimation 类不可少的函数,由这个函数可知,要做到绘制 Sprite 轮廓时丝毫不影响其背景图像和掩膜是必不可少的。除开图像和掩膜,Sprite 类还包括x,y位置,以及其他一些记录它在背景上的运行轨迹的变量。
一个Sprite对象还有它自己的显示优先级。显示优先级用整型量表示,存储在成员变量disp_ priority中,它决定了绘制重叠子图形的次序——优先级高的子图形被绘制到优先级低的子图形的上面。
另一个成员变量,DRAWPROC类型的dproc很有意思,它由下面的typedef语句声明:
typedef void (_FAR PASCAL *DRAWPROC) (HDC hdc,short x,short y,LFVOID data)
可见,dproc 是指向函数的指针。在需要绘制 Sprite 图像的任何时候都会调用dproc指定的函数。您可以绘制对象——例如直线、矩形、椭圆或文本——这样一个子图形就可以有多个位图式图像,例如应用程序 ANIMATE 显示移动文本,就用到了 Sprite 的这个特性。

Sprite类继承了Borland类库(CLASSLIB)中的Sortable类,由于只有Sortable 对象才可以放在SortedArray(Borland CLASSLIB 的另一个类)中,继承后就可以把Sprite 对象存储在SortedArray 中。由于继承了Sortable ,Sprite 类必须定义以下的成员函数:
classType isA ( ) const ;
char_FAR * nameof( ) const ;
hashValueType hash Value( ) const ;
void printOn (ostream_FAR& os) ;
这四个函数的定义都很简单。函数的isA返回唯一的整型标识符,表示Sprite类,函数nameOf返回类名,即字符串“Sprite”。
此外,为了Sprite对象能够正确地排序,Sprite类必须包括下面的函数:
int isLessThan (const Object_FAR& ob ) const ;
int isEqual(const Object_FAR& ob ) const ;
函数isLessThan用来检验某个Sprite是否“小于”另一个。正如程序所定义的那样,isLessThan通过比较子图形的是显示优先级来决定哪个子图形“小些”。
初始化 Sprite
创建和初始化Sprite的典型方法是使用构造函数,它以图像文件名和掩膜文件名为多数。
Sprite::sprite (HDC hdc,LPSTR imagefilename
,LPSTR maskfilename,short priority)
该构造函数调用init_image函数装入相应的图像位图和掩膜位图。构造函数还要求有设备场景的句柄。这是因为图像位图和掩膜位图要转换为设备无关格式,这一步骤需要一个DC。
函数init_image从文件中装入图像。它通过文件扩展名来判断图像类型。
该函数接受下列扩展名:
.BMP 即 Windows DIB 文件。
.PCX 即 PC PaintBrush 文件。
.TGA 即 24 位,Truevision Targa 文件。

上面介绍了如何生成背景图像,子图形的基本概念。这一节让我们看一下怎样创建一个或两个子图形,并把它们添加到场景中去。
为何需要通知视图在场景中装入了一个新的子图形有两个原因:首先,子图形只有被画出来才能成为可见的,而第二,子图形中的颜色必须被映射到由屏幕视图缓冲区使用的调色板中。
注意,即使背景和子图形使用的是同一个调色板,但由于考虑到系统颜色和调色板项的逻辑到物理映射之间的作用方式,仍然需要将子图形映射到屏幕缓冲区调色板上。屏幕缓冲区中的调色板是由背景DIB中的颜色表而创建的。当调色板成为一种等同调色板时(出于最好性能的考虑),可以对颜色进行重新排序,因此背景DIB和所有子图形中的象素必须被改变以具有在新的(等同)调色板中的正确索引。如果你没有将子图形的颜色映射到屏幕缓冲区中,那么显示出来的子图形颜色将都是错误的。
首先,让我们看一看简单的CAnimView: :NewSprite函数:
void CAnimView::NewSprite(CSprite* pSprite)
{ kk1}
// Map the colors in the sprite DIB to the
// palette in the off-screen buffered view.
if (m_pPal) {kk1}
pSprite->MapColorsToPalette(m_pPal);
}

// Render the scene with the new sprite.
Render();

// Draw the result.
Draw();
}
通过调用MapColorsToPalette函数,DIB的颜色被映射到调色板上。调用Render函数在屏幕缓冲区构画子图形图象,然后Draw函数将缓冲区拷贝到屏幕上,使子图形在场景中可见。Map Colors To Palette函数和我们为CDIB类开发的函数几乎完全一样:
BOOL CSprite::MapColorsToPalette(CPalette* pPal)
{kk1}
BOOL bResult = CDIB::MapColorsToPalette(pPal);
// Get the transparency info again.
Initialize();
return bResult;
}
事实上,你可以看到CDIB: :Map Colors To Palette被调用来进行实际的映射,而CSprite类所特有的唯一工作是再次获取透明信息,这是因为我们使用的是调色板索引来定义子图形图象中的哪种颜色是透明的,因而需要在调用Map Colors To Palette时被重新映射。
需要对Render函数进行一点小的改进以处理在构画背景图象之外还得构画子图形。
void CAnimView::Render(CRect* pClipRect)
{kk1}
CAnimDoc* pDoc = GetDocument();
CRect rcDraw;

// Get the background DIB and render it.
CDIB* pDIB = pDoc->GetBackground();
if (pDIB) {kk1}
pDIB->GetRect(&rcDraw);
// If a clipping rectangle was supplied, use it.
if (pClipRect) {kk1}
rcDraw.IntersectRect(pClipRect, &rcDraw);
}

// Draw the image of the DIB to the off-screen buffer.
ASSERT(m_pDIB);
pDIB->CopyBits(m_pDIB, 
rcDraw.left,
rcDraw.top, 
rcDraw.right - rcDraw.left,
rcDraw.bottom - rcDraw.top,
rcDraw.left, 
rcDraw.top);
}

// Get the sprite and render it.
CSprite* pSprite = pDoc->GetSprite();
if (pSprite) {kk1}
pSprite->Render(m_pDIB, &rcDraw);
}
}
背景图象被首先画到缓冲区。如果存在一个子图形,那么它也被画到屏幕缓冲区,位于背景之上。这就是本书中所说的“面向对象欺骗”。我们并没有书写大量的代码来将子图形的图象画到屏幕缓冲区,而只是让子图形自己来完成这一工作。这样作意义重大,因为这种作法使子图形的特有代码集中在子图形对象中,从而使我们可以将注意力放在功能上而不必纠缠在实现细节上。
当然,还得我们来书写子图形的构图代码,因此让我们看一看下面的代码:
void CSprite::Render(CDIB* pDIB, CRect* pClipRect)
{kk1}
ASSERT(pDIB);

// Get the sprite rectangle.
CRect rcDraw;
GetRect(&rcDraw);

// If a clipping rectangle was supplied, see if the sprite
// is visible inside the rectangle.
if (pClipRect) {kk1}
if (!rcDraw.IntersectRect(pClipRect, &rcDraw)) {kk1}
return; // Not visible
}
// Copy the image of the sprite.
CopyBits(pDIB, // Dest DIB
rcDraw.left, // Dest x
rcDraw.top, // Dest y
rcDraw.right - rcDraw.left,// Width
rcDraw.bottom - rcDraw.top,// Height
rcDraw.left - m_x, // Source x
rcDraw.top - m_y, // Source y
PALETTEINDEX(m_bTransIndex)); 
// Transparent color index
}
当然我们需要检验子图形是否在裁剪板矩形中是可见的(如果提供了裁剪板矩形的话)。一旦计算出实际的绘图矩形后,CDIB::CopyBits函数被用来将子图形图像的非透明部分拷贝到屏幕缓冲区中。CDIB::CopyBits的透明特征是一种新的特性,请读者在动画设计时注意。

为了实现重画区域,需要将一个区域列表保存在某个位置,以及一种往列表中添加列表项的方法,而当要执行一次构图和画图动作时,需要一种遍历列表的方法。因为构图和画图主要涉及到COSBView类中的屏幕缓冲区,因此作者决定在COSBView中增加重画列表及其管理函数。
首先来看一看应用程序的其余部分是如何使用重画列表的,然后再进一步讨论重画列表的函数是如何实现的。先看一看下面的这个代码,此代码实现了对ANIMVIEW. CPP中一个子图形的鼠标拖动:
void CAnimView::OnMouseMove(UINT nFlags, CPoint point)
{kk1}
if (m_bMouseCaptured) {kk1}
ASSERT(m_pCapturedSprite);
CRect rcOld, rcNew;
m_pCapturedSprite->GetRect(&rcOld);
m_pCapturedSprite->SetPosition(point.x - m_ptOffset.x, 
point.y - m_ptOffset.y);
m_pCapturedSprite->GetRect(&rcNew);
// Add the changed regions to the view's dirty list.
AddDirtyRegion(&rcOld);
AddDirtyRegion(&rcNew);
// Render and draw the changes.
RenderAndDrawDirtyList();
}
}
子图形的位置被保存在一个CRect对象中。移动子图形,并获得其新的位置。通过调用AddDirtyRegion,将新旧矩形添加到重画列表中。一旦这些区域被添加到列表中,将产生对RenderAndDrawDirtyList的一个调用从而画出改变后的区域。AddDirtyRegion和RenderAndDraWDirtyList都是COSBView中的新函数。
COSBView类具有一个新的对象来保存重画列表和几个新的成员函数。下面是COSBVIEW. H的新添加的部分:
class COSBView : public CScrollView
{kk1}

public:
virtual void Render(CRect* pClipRect = NULL) {kk1}return;}
void AddDirtyRegion(CRect* pRect);
void RenderAndDrawDirtyList();

private:
CObList m_DirtyList; // Dirty regions

void EmptyDirtyList();
};

#ifndef _DEBUG // Debug version in osbview.cpp
inline CDocument* COSBView::GetDocument()
{kk1} return (CDocument*) m_pDocument; }
注意重画列表是作为由Microsoft基类库(MFC)所提供的一个CObList对象实现的。CObList类保留了一个对象指针的列表,而其中每一个对象又都是从CObject类导出的。此表在这用于保存指向CRect对象(重画区域在这里的实现)的指针,而通过创建一个由CObList导出而只处理CRect对象的类,从而使实现更多了一些类安全性。采用这种方法要求进行类型强制,因此对新手来说也更多了一些危险性。现在来看一看AddDirtyRegion函数,这样可以了解列表的使用方法:
void COSBView::AddDirtyRegion(CRect* prcNew)
{kk1}
// Get the rectangle currently at the top of the list.
POSITION pos = m_DirtyList.GetHeadPosition();
if (pos) {kk1}
CRect* prcTop = CRect*)m_DirtyList.GetNext(pos);
CRect rcTest;
// If the new one intersects the top one merge them.
if (rcTest.IntersectRect(prcTop, prcNew)) {kk1}
prcTop->UnionRect(prcTop, prcNew);
return;
}
}
// List is empty, or there was no intersection.
CRect* prc = new CRect;
*prc = *prcNew; // Copy the data.
// Add a new rectangle to the list. . m_DirtyList.AddHead((CObject*)prc);
}
记住在这里使用的唯一优化是如果一个新添加的矩形和重画列表中位于其上面的那个矩形之间存在重叠,则进行合并。代码首先获得位于列表顶部的矩形——注意CRect*强制,如果列表是空的,那么新的矩形被简单地添加到列表的顶部,如果在列表的顶部
已经存在有一个矩形,则检测该矩形是否和新矩形之间有相交。如果有,则简单地进行扩大以包括新的矩形。如果没有发现有交叉,则将新矩形添加到列表的顶部。
现在,让我们再来看一看
RenderAndDrawDirtyList函数:
void COSBView::RenderAndDrawDirtyList()
{kk1}
POSITION pos = m_DirtyList.GetHeadPosition();
// Render all the dirty regions.
while (pos) {kk1}
// Get the next region.
CRect* pRect = (CRect*)m_DirtyList.GetNext(pos);
// Render it.
Render(pRect);
}
// Draw all the dirty regions to the screen.
while (!m_DirtyList.IsEmpty()) {kk1}
// Get the next region.
CRect* pRect = (CRect*)m_DirtyList.RemoveHead();
Draw(NULL, pRect);
// Done with it.
delete pRect;
}
}
对重画列表中的每一个矩形,将它作为一个剪切区域的矩形传递给Render函数,进行函数调用。然后再遍历一次重画列表,这次是通过调用一次Draw函数,将被构图的缓冲区区域拷贝到屏幕上。
为了能够将这个函数包含在COSBView类中,作者还增加了一个模拟Render函数。这是一个虚拟函数,由用户提供一种实现来覆盖它。下面是在头文件中实际定义的模拟函数:
virtual void Render(CRect* pClipRect = NULL) {kk1}return;} 
现在来看一看CAnimView中真正的Render函数:
void CAnimView::Render(CRect* pClipRect)
{kk1}
CAnimDoc* pDoc = GetDocument();
CRect rcDraw;

// Get the background DIB and render it.
CDIB* pDIB = pDoc->GetBackground();
if (pDIB) {kk1}
pDIB->GetRect(&rcDraw);
// If a clipping rectangle was supplied, use it.
if (pClipRect) {kk1}
rcDraw.IntersectRect(pClipRect, &rcDraw);
}
// Draw the image of the DIB to the off-screen buffer.
ASSERT(m_pDIB);
pDIB->CopyBits(m_pDIB, 
rcDraw.left,
rcDraw.top, 
rcDraw.right - rcDraw.left,
rcDraw.bottom - rcDraw.top,
rcDraw.left, 
rcDraw.top);
}

// Get the sprite and render it.
CSprite* pSprite = pDoc->GetSprite();
if (pSprite) {kk1}
pSprite->Render(m_pDIB, &rcDraw);
}
}
在这个函数中没什么新东西,但作者想再显示一次Render,这样用户可以看到剪切矩形是如何被用来限制缓冲区的受影响区域的。


这一章里,我们简单介绍了计算机动画制作的基础知识,并通过上述几个例程讲解了如何在Windows 下实现简单动画过程。实际上要想做出真正精美的动画,或是进行游戏创作还需要借助应用软件和图形处理工具。
象电视电影一样,有很多动画软件将动画过程分解为不同的图片,再把它们连起来播放,达到动画效果。在这里,我们介绍一下,如何方便的制作出三维动画。在三维图像处理软件3D MAX中提供了将操作过程连接城动画的功能,利用这一点,我们可以很容易的制作出逼真的三维动画。
这一节我们制作一个简单的动画,一只茶壶从飘着雪花的雪山下飞出。由于具体的制作步骤依存放于历程中,具体的制作过程也在教程中说明。这里我们只是对教程中录制动画时帧数的选取作以补充说明。由于3D MAX自动会将指定帧数到调整前帧数或是动画的开始(0帧)之间物体的移动、旋转、变形记录下来。所以一开始我们将帧数改为30,然后移动壶从山底到山顶,这一过程就表现为从0帧到30帧壶从山底飞出。而此后将帧数改为80,将壶拉近屏幕并旋转,就停止录制。我们可以看到飞出山顶的壶又飞近屏幕并转了近180度,在原地停留了一会又重新从山底飞出。

总的来说,动画制作的方法有很多种,而且功能更强的工具软件也一定会层出不穷。只要你有好的创意,就一定会制作出优秀的动画。

DirectDraw是微软新近发行的DirectX3软件开发工具箱(SDK)中的一部份。对那些不甚了解的人来说,DirectX3SDK是原来称为游戏SDK的最新版本。和在游戏SDK中一样,DirectX3SDK包含了一组动态链接库,用来图形加速,3─D图形服务,声音加速,扩展连接,游戏杆操纵以及CD─ROM自动化。
关于DirectX3SDK可以有很多的话题讨论,本篇只涉及如何用DirectDraw来编写游戏中的图形部份。尽管需要一些有关OLE和构件模型(COM)接口的基础知识,整个过程还是相当简单的。而且,所需的有关OLE和COM接口的知识也将在此讨论到。
在这里,我们只是讨论如何使用最基本的DirectDraw函数。所举的例子仅涉及到全屏和翻页,对于如何在窗口中使用DirectDraw,在3维图中使用文字,使用视频剪接或是用Direct3D生成表面不作过多介绍。
DirectX3SDK的基本环境
DirectX3SDK可以在Windows95或WindowsNT4.0中使用。基于本文的目的,假定使用的环境是IBM─PC和Windows95。另外,还需要安装DirectX3SDK,以便编译连接应用。
同时,使用的C或C++编译器应能生成32位的应用。您也可以使用其他的语言,但这里并不想涉及。当然您还应当具有Windows的编程技术。如果使用的是C编译器,则还必须包含Win32SDK。Win32SDK包含了生成可执行程序时的一些库。
DirectDraw,OLE,和COM接口
DirectDraw是围绕着OLE和COM接口来设计的。如果您不熟悉OLE编程,那将很难开始用DirectDraw编程,这是最基本的一点。尽管在很多书上解释了很多概念,介绍OLE和COM接口是如何工作的,但要用好DirectDraw其实只要了解有限的一些。
首先看看OLE和COM的定义。OLE是微软为在不同的进程和机器间共享信息和服务而引进的基于对象的技术。COM指的是构件对象模型,在OLE编程中,它是接口模型。
DirectDraw创建了函数来负责增加引用计数并返回指向接口的指针。但您仍要对没一个隐式创建的接口指针执行Release操作。如果没有维护好对象的引用计数,将很容易引起内存泄露。在后面编码的例子中,将介绍如何进行。
下面看一个使用IDirectDraw接口方法的C程序行。
ddrval=lpDD->lpVtbl->SetDisplayMode(lpDD,ScreenX,ScreenY,ScreenBpp);
程序用了SetDisplayMode方法来设置显示模式,返回值表示成功或失败。在此重要的一点是如何取得指向方法的指针,不能直接访问IDirectDraw接口方法。当一对象实例化时,它生成一个虚函数表,称为vtable,它包含了指向该对象接口方法的所有指针。在上例中,指向DirectDraw对象的指针(lpDD)指向了指向vtable的指针(lpVtbl),vtable中包含了对象所有方法的指针,具体地说,本例中指向了SetDisplayMode方法。应用和方法接口间的联系可以清楚地看成这样:
下一个例子看看如何用C++来做:
ddrval=lpDD->SetDisplayMode(ScreenX,ScreenY,ScreenBpp);
注意vtable不再显式地使用,C++自动地将对象(lpDD)作为第一个参数。不需要this指针,因为C++用指向当前对象的指针(本例中为lpDD)隐式地执行该方法。


在使用DirextDraw时,需要首先创建一个对象DirectDraw的实体,该对象实体代表了微机显示适配器。然后,使用接口所提供的方法来操作该对象实体,使之完成有关命令和任务。接着,你还需要创建一个或多个DirectDraw-surface对象的实体,以便能在图形表面(Surface)上展示你的游戏画面。
下面,在例程DDEX1中展示如何使用Directx3SDK来DirectDraw对象实体,如何创建一个带有后台缓冲区的基本表面(Surface),以及如何弹出表面(Surface)。
注意:所有的例程都是用C++写成的,如果你的编辑器是C,你需要在文件中作出某些改动(至少,你要加入Vtable和指向各种接口方法的this指针)。
DirectDraw初始化:
DirectDraw 初始化代码写在dolnit函数中。
/**CreatethemainDirectDrawobject.*/
ddrval=DirectDrawCreate(NULL,&lpDD,NULL);
if(ddrval==DD_OK)
{kk1}
//Getexclusivemode.
ddrval=lpdd->SetCooperativeLevel(hwnd,
DDSCL_EXCLUSIVE|DDSCL_FULLSCREEN);
if(ddrval==DD_OK)
{kk1}
//Createtheprimarysurfacewith1backbuffer.
ddsd.dwSize=sizeof(ddsd);
ddsd.dwFlags=DDSD_CAPS\DSD_BACKBUFFERCOUNT;
ddsd.ddsCaps.dwCaps=DDSCAPS_PRIMARYSURFACE|
DDSCAPS_FLIP|
DDSCAPS_COMPLEX;
ddsd.dwBackBufferCount=1;
ddrval=lpDD->CreateSurfae(&ddsd,&lpDDSPrimary,
NULL);
if(ddrval==DD_OK)
{kk1}
//Cetapointertothebackbuffer.
ddscaps.dwCaps=DDSCAPS_BACKBUFFER;
ddrval=lpDDSPrimary->GetAttachedSurface(&ddscaps,&lpDDSBack);
if(ddrval==DD_OK)
{kk1}
//Drawsometext.
if(lpDDSPrimary->GetDC(&hdc)==DD_OK)
{kk1}
SetBkColor(hdc,RGB(0,0,255));
SetTextColor(hdc,RGB(255,255,0));
TextOut(hdc,0,0,sxFrontMsg,lstrlen(szFrontMsg));
lpDDSPrimary->ReleaseDC(hdc);
}
if(lpDDSBack->GetDC(&hdc)==DD_OK)
{kk1}
SetBkColor(hdc,RGB(0,0,255));
SetTextColor(hdc,RGB(255,255,0)):
TexOut(hdc,0,0,szBackMsg,lstrlen(szBackMsg));
lpDDSBack->ReleaseDC(hdc);
}
//Createatimertoflopthepages.
if(SetTimer(hwnd,TIMER_ID,TIMER_RATE,NULL))
{kk1}
returnTRUE;
}}}}}
wsprintf(buf,"DirectDrawInitFailed(%08lx)\n",ddrval);
以下针对初始化DirectDraw对象和准备表面(Surface)集的各个步骤分别进行讨论。

为了创建一个DirecDraw对象实体,你应该在程序中使用DirectDrawCreateAPI函数(注意:这里我所说的是应该,而不是必须),这是因为使用OLE中的CoCreatelnstance函数也能创建一个DirectDraw对象实体,但这不在我们的讨论范围之中)。DirectDrawCreate采用全球统一的标准,它代表显示设备,这些显示设备在大多数情况下被定为NULL(即:系统使用缺省的显示设备)。当DirectDraw对象实体创建好后,就会有一个指针指向该对象实体。而且,在调色板中有三分之一的指针指向NULL(这样做的目的是为了今后的扩展)。
接下来的例子说明如何创建一个DirectDraw对象,并判别该对象是否创建成功:
ddrval=directDrawCreat(NULL,&lpDD,NULL);
if(ddrval==DD_OK)
{kk1}
//lpDDisavalidDirectDrawobject.
}
else
{kk1}
//DirectDrawobjectcouldnotbecreated.
}
使用IDirectDraw2和IDirectDrawSurface2接口
在你读本文的其他部份时,你会注意到所有的例程都使用的是IDirectDraw和IDirectDrawSurface的老版本接口。这是因为DirectX3SDK所给出的例程还没有来及使用IDirectDraw和IDirectDrawSurface更新后的接口。你可以通过调用IDirectDraw::QueryInterface方法来得到IDirectDraw2和IDirectDrawSurface2接口。
下面的代码给出如何得到IDirectDraw2接口:
//CreateanIDirectDraw2interface.
LPDIRECTDRAWlpDD;
ddrval=DirectDrawCreate(NULL,&lpDD,NULL);
if(ddrval!=DD_OK)
return;
ddrval=lpDD->SetCooperativeLevel(hwnd,DDSCL_NORMAL);
if(ddrval!=DD_OK)
return;
ddrval=lpDD->QueryInterfave(IID_IDirectDraw2,(lPVOID*)&lpDD2);
if(ddrval!=DD_OK)
return;
下面的代码给出如何得到IDirectDrawSurface2接口:
LPDIRECTDRAWSURFACElpSurf;
LPDIRECTDRAWSURFACElpSurf2;
//Createsurfaces.
memset(&ddsd,0,sizeof(ddsd));
ddsd.dwSize=sizeof(ddsd);
ddsd.dwFlags=DDSD_CAPS|DDSD_WIDTH|DDSD_HEIGHT;
ddsd.ddsCaps.dwCaps=DDSCAPS_OFFSCREENPLAIN|
DDSCAPS_SYSTEMMEMORY;
ddsd.dwWidth=10;
ddsd.dwHeight=10;
ddrval=lpDD2->CreateSurface(&ddsd,&lpSurf,NULL);
if(ddrval!=DD_OK)
return;
ddrval=lpSurf->QueryInterface(
IID_IDirectDrawSurface2,(LPVOID*(&lpSurf2);
if(ddrval!=DD_OK)
return;
                                                     

    三维世界

  


到目前为止,本书已经介绍了Windows下制作图像动画的技术。现在转向对三维图形介绍,三维图形是许多多媒体应用程序和计算机游戏的主体部分。本章将开发一些C++类来表示三维形体,并且把这些类用到一个显示三维形体的例程中。尽管计算机图形学使用射线追踪技术可以生成真实画面的显示,但本章的重点只放在如何显示由三边形成四边形定义的,并且实际可行的复杂形体。本章在给出三维坐标变换和向量运算的简单描述之后,接着给出表示三维形体的C++类。了解三维图形的详细情况,特别是有关空间几何学的问题。 

要描绘三维物体就得有三维坐标系。描绘三维坐标系,最好先从二维坐标系统开始,您可能很熟悉二维笛卡尔坐标系。如果把书页想象成一个平面,在定义二维坐标系时可以先选定一点作为原点。按通常的做法,选取书页的左下角作为原点。然后可以把书页下底边沿作为X轴,左边沿作Y铀。请注意两坐标轴是相互垂直的。一旦定义了原点和坐标轴,就可以用指定坐标的方法指定页上的任意一点的位置,点的坐标为分别沿X轴和Y轴的距离。由下图可见X坐标是该点沿X轴的距离Y坐标是该点沿Y轴的距离。点在平面上的位置常表示为(x,y),其中x和y就是X坐标和Y坐标。

通常所用的三维笛卡尔坐标系是二维笛卡尔坐标系的扩展。三维坐标系又另加了一轴——Z轴,它垂直于x轴和Y轴决定的平面。如果把x轴的正方向想象为书页的右方向,把Y轴的正方向想象为书页向上万向,那么z轴的正方向即可以指向书页内,又可以指出书页外。按右手法则,Z轴正方向应该是指出书页外(见图)。
在图形学和工程学的规则中,右手法则是表达和操作三维坐标的标准数学约定。不过只要您清楚所选坐标系的含义,并且做到前后一致,那么具体选择什么坐标系其实并不重要。

建立三维物体模型的一个方法是指明构成物体边界的各个表面。尽管大多数现实物体由曲面构成,但是使用简单的多边形去逼近物体边界还是可能的。本章使用多边形集来表示物体边界,当然许多简单物体的边界本来就是平面,像立方体,甚至是有曲面的物体也可以由大量平面逼近,如圆柱体和球体。尽管如此,本书重点不是在实际物体模型上,而是可用在游戏中的足够简单的模型。
多边形界定的物体在许多计算机游戏中用得很广,例如使用三维动画的飞行模拟器,在绘出适当的阴影后,多边形也可经获得足够的真实效果,并且它们在操作时计算都很有效。

多边形是由它的角——也即项点——定义的。每个项点是一个三维点。与二维点类似,三维点也是由X,Y,Z三个坐标表示的,通常写成(x,y,z)。要用多边形定义物体,就得先从取物体的三维坐标系开始。下图表明如何定义一个单位立方体——即各边长度均为“1”的立方体。 

当您在构造复杂的三维景物时,也许希望把同一物体复制到景物中的不同位置,也许还要对物体进行比例变换和旋转,要完成这些功能,就需要进行三维坐标变换。首先为景物定义坐标系。然后对单个物体分别进行比例变换、旋转或平移,以分别达到各个物体不同的要求。例如我们下面所看到的这包含一个圆柱体和一个圆锥体的三维景物——每个三维物体进行不同的方式的比例变换,旋转和定位,我们可以将他们通过变形组合成一支毛笔。

用来在计算机上构造3D模型的常用方法是B-rep和CSG方法:
1.B-rep造型
B-rep是有界表现(boundary-representation)的缩写(头字语)。3- D子程序仅仅描绘对象的外层表皮——边界,B-rep模型的表面(外表)由小平面或多边形构成。小平面群可以结合在一起建立不同的形状、曲线或复杂的模型。
B-rep造型不仅容易在一台个人计算机上实现,而且它是快速的,并能产生现实主义的想像力。隐藏的表面迁移可使用不同的算法来实现。
B-rep技术具有限制之处。将两个模型结合在一起或通过一个现有模型来钻出一个孔(hole)从数学上是困难的。B-rep模型算法仅考虑对象的边界,并不是实体容量或内部文章。
2.CSG造型
CSG是结构实体几何学(constructive solid geometry)的缩写(头字语)。CSG模型建立起3-D实体,诸如立方体、平行六面体、柱体、球体等等。由于这些子对象的每一种也是一个3-D实体,这些子对象可被组合一起产生更复杂的甘象。CSG考虑一个对象的体积结构,所以软件可在模型中钻一个孔,或者执行其它的操作。一个柱体代表想要的孔,而在数学上是从模型中减去。这类3-D逻辑操作称为一种3-D布尔(Boolean)操作或欧拉(Euler)操作。
CSG模型化用于这样的应用方面,即重量、质量、惯性矩、密度和其它基本工程的要素是重要的。CSG模型化是真正的实体模型化,而且不像B-rep造型,CSG是非常耗时的和存贮器集中的(内存大)。
图形用户输入
3D图形软件经常给用户这种能力,在建立3D模型期间同软件发生交互作用。这种创造的交互作用常常取决于压延(extrusion)和子对象。
1.旋转(Revolve)、压延(extrude)和弯曲(sweep)
3D函数relvolve、extrude和sweep用于从一个2D外形构造3D对象。用户指定一个2D形状,然后指出沿什么方向旋转外形以及旋转多少度。软件负责产生3D对象。这个旋转函数对于B-rep和CSG模型两者都能很好工作。
生更复杂的甘象。CSG考虑一个对象的体积结构,所以软件可在模型中钻一个孔,或者执行其它的操作。一个柱体代表想要的孔,而在数学上是从模型中减去。这类3-D逻辑操作称为一种3-D布尔(Boolean)操作或欧拉(Euler)操作。
CSG模型化用于这样的应用方面,即重量、质量、惯性矩、密度和其它基本工程的要素是重要的。CSG模型化是真正的实体模型化,而且不像B-rep造型,CSG是非常耗时的和存贮器集中的(内存大)。
图形用户输入
3D图形软件经常给用户这种能力,在建立3D模型期间同软件发生交互作用。这种创造的交互作用常常取决于压延(extrusion)和子对象。
1.旋转(Revolve)、压延(extrude)和弯曲(sweep)
3D函数relvolve、extrude和sweep用于从一个2D外形构造3D对象。用户指定一个2D形状,然后指出沿什么方向旋转外形以及旋转多少度。软件负责产生3D对象。这个旋转函数对于B-rep和CSG模型两者都能很好工作。

3D图形软件经常给用户这种能力,在建立3D模型期间同软件发生交互作用。这种创造的交互作用常常取决于压延(extrusion)和子对象。
1.旋转(Revolve)、压延(extrude)和弯曲(sweep)
3D函数relvolve、extrude和sweep用于从一个2D外形构造3D对象。用户指定一个2D形状,然后指出沿什么方向旋转外形以及旋转多少度。软件负责产生3D对象。这个旋转函数对于B-rep和CSG模型两者都能很好工作。
extrude函数也取一个2D外形并转动它成为一个3D对象。用户规定向前延伸或挤压该外形有多么深。extrude函数也工作于B-rep和CSG造型两者。
sweep函数类似于extrude,除了延伸沿着一条曲线发生而不是像extrude函数的直线以外。
2.二次原体(Quadric primitives)
Revolve、extrude和sweep全部都依赖于用户的输入。某些软件采取不同的途径,在一个3D工具套件(toolkit)中提供准备使用的子对象。用户选择一个希望的子对象井然后通知3D软件子对象定位在何处。典型的子对象也称为二次原体,包括柱体、锥体、球体、双曲线体、抛物线体、平行六面体、楔形体、棱锥体、层次体、圆环等。用户可处理每个原体的大小和比例。
3.曲面(curved surfaces)
平滑曲面可由B-rep和CSG造型系统两者来构成。一个直纹曲面是通过在两条曲线之间构造一组直线而建立的曲面。这两条线通常是参数曲线,其最终点和控制点已被最终用户所规定。一个立方体转接(patch)曲面是一个由四个弯曲的边缘为界的曲面。
4.欧拉(Euler)操作
欧拉操作也称为3D布尔操作,模拟真实世界的工作方式。欧拉操作包括结合(joining)、相交(intersection)和相减(subtraction)。
结合表明两个实体彼此连结。相交表示当两个实体被连结时从共有容积所得到的实体。相减意指从另一个实体中移去一个实体的容积。

利用3D影像的C++图形程序使用xyz坐标来描述形状和环境。x 维描述左方和右方测量,y坐标描述上方和下方测量。z 坐标描述近、远测量。
(l)对象和世界坐标
对象坐标是描述3D对象基本形状的xyz维。世界坐标是描述对象的xyz坐标,当对象被放置在一个指定的3D环境中某个位置和旋度上。所以称为世界坐标是因为3D环境被称为3D世界。
(2)摄影机和彩像面坐标
摄影机(Camera)坐标是xyz坐标,它描述该对象对于一个观察者如何出现在3D环境中的指定位置。摄影机位置被称为视点(观察点)(Viewpoint)。
影像面坐标是xy坐标,它将出现在一个两维的设置在摄影机和3D对象之间的影像面(或窗口)上。
(3)屏幕坐标
屏幕坐标是x y坐标,它能够在计算机显示屏幕上报绘出来。屏幕坐标也称为显示器(display)坐标,它是将影像面定标以适合显示屏幕的结果。
在这个3D操作过程中计算每组坐标都包括从矩阵数学导出的正弦(sine)和余弦(cosine)公式。

除了以基本图形编程技术为基础的实体之外,希望建立一个3D图形程序的任何程序员都需要了解3D编程概念。
就像目测图形广泛领域内的任何其它训练一样,3D图形具有它自己的体系。下面我们介绍这些3D编程术语:
(l)造型(Modeling)
造型就指描绘3D对象的形状的作用(行动)。造型本身牵涉到获取正确的形状和体积。
坐标是xyz测量,它表示在模型上的一个特定的点。坐标系统意指用于测量宽度、高度和深度的xyz轴系统——3D的三维。许多矛盾的坐标系统在今天使用着。
(2)扮刷(Rendering)
粉刷意指用一种适合于布景中光源方式对3D模型涂阴影或涂彩色。隐藏面迁移和可视面检测适用于描绘3D对象的技术,因此被别的对象面所隐藏的表面或背后面正确地被画出或被放弃(当情况需求时)。
实体造型参照这佯的3D算法,它将每一个对象作为一个有质量的实体来处理。结构实体几何学(CSG)使用实体造型技术。B-rep造型使用小平面或多边形从对象的表层或边界来构造实体。建立和处理3D模型
3D图形依赖于xyz坐标,它被用来描述3D空间中的唯一位置。然而,存在不同类型的3D空间。一个模型必须跟随通过每种类型3D空间的一条展开的路径,直到完成的模型能在计算机屏幕上显示出来为止。

通过上面的介绍,我们了解到:要实现从二维到三位的转换,不但要经过繁杂的数学转换、矩阵运算;为了在屏幕上生成这些三维图形,还需要建立大量的图形函数。所以,通常一个程序设计者,在编制三维图形程序时,都先建立功能齐备的数学函数库和图形函数库。
一般来说,数学函数库中应包含三大部分函数:
数学函数,这部分函数用来对数字进行运算和操作;
向量和矩阵函数,这些函数用来生成并控制向量,三维物体的生成大多由此部分函数完成;
仿射变换,这些函数使用一个4×4的矩阵对点的位置进行转换,也就是平常所说的平移、旋转等函数。
这里我们介绍一个库函数中较常见的一个函数——向量旋转函数:void Rotate3D (int m, float Theta, Matx4×4 A)
{kk1}
int m1,m2;
float c,s;
ZeroMatrix(A); 
//使一个4×4的矩阵中所有元素为0
A[m-1,m-1]=1.0;
A[3,3]=1.0;
m1=(m%3)+1;
m2=(m1%3);
m1-=1;
c=CosD(Theta); 
//求用度指定的角的余弦
s=SinD(Theta); 
//求用度指定的角的正弦 
A[m1,m1]=c;
A[m1,m2]=s;
A[m2,m2]=c;
A[m2,m1]=-s;

Rotate3D函数为实现一个向量的旋转生成一个矩阵。我们可以看到在定义此函数时,用到了函数库中前面定义的更简单的函数,使得函数定义十分容易。可见建立一个完备的函数库使用起来会非常方便,而且通过函数由简单到复杂的逐步建立,生成一个函数库并不是一件难事。读者可参照上面讲到的步骤,根据自己绘图的需要,由简单的数学函数、矩阵生成开始,通过这些很小的函数去堆积成具有很强功能的函数。

上一节我们介绍了数学函数库的建立方法,这里,让我们再来看一下如何建立图形函数库。
图形函数库的建立要比数学函数库的建立复杂一些,它不但要求包括画点画线的函数,还要解决信息的存储问题,颜色的调用和对平面作复杂的数学模型转换。
建立图形函数库也主要应从以下几个方面入手:
预置一个数组,确定转换信息存储地址所需要的偏移量。这样可以减少绘图时的计算量。
预制调色板数组,并在寄存器中指定合适的显示内存地址。并结合显示硬件编制合适的色彩调用程序。还应注意屏幕的初始化。
定义一些最基本的图形绘制函数,如画点、画线、画矩形、圆形等。
最复杂的是最后一步,你可能需要建立基于复杂的数学公式的图形转换,对前面提到过的那些图形特殊处理,甚至需要构造复杂的数学曲面。当然这样,你在建立数学函数库时也要费一番功夫。
当然,如果你只是做一个很简单的图形设计,则完全没必要这么麻烦,你可以不必下大功夫去管信息的合理存储,色彩的调用(注意,屏幕的初始化却是必不可少的)。你只需构造几个简单的画图函数,其余工作在主函数中以简练的语言来实现。
这里,我们来看一个初始化屏幕的例子:
void InitGrapics( )
{kk1}
Xres=MaxXres;
Yres=MaxYres;
PreCalc( ); 
//预制信息存储偏移量的数组
SetMode(19); 
//设置显示方式
ClearPalette(Color); 
//将调色板中红、绿、蓝的值清零
InitPalette(Color); 
//调整颜色强度,设置颜色标志
SetPalette(Color); 
//将定义颜色传送到寄存器 
}
对于简单图形的实现,在图像编程一章中已作了介绍,这里不再另外讲解。
以上我们介绍了三维图形设计的基本概念和常用方法,下面我们将以几个程序来看一下绘制三维图形常用的一些语句和算法等。首先我们来看一下这个小程序,运行下面这个程序将绘出一个具有立体感的网状图形:
include
main()
{kk1}
int graphdriver=DETECT,graphmode;
int a=100;
int b=10;
int x,y;
int i;
initgraph(&graphdriver,&graphmode,"");
x=y=a;
for(i=0;i<11;i++)
{kk1}
asteroid(x,y);
x+=b;
y-=b;
}
x=y=a;
for(i=0;i<11;i++)
{kk1}
asteroid(x,y);
x-=b;
y+=b;
}
getch();
closegraph();
asteroid(x,y)
int x,y;
{kk1}
line(320,100-y/2,320-x,100);
line(320-x,100,320,100+y/2);
line(320,100+y/2,320+x,100);
line(320+x,100,320,100-y/2);
} 
下面我们再来看一下这个椭圆子程序的设计,根据数据关系式,用C语言编写椭圆子程序,清单如下:
#define rad 3. 1415926/180
void elipse (x0,y0,a,b,start,bfe,efe,inc)
int inc;
float x0,y0,a,b,start,bfe,efe;
{kk1}
float sta,bf,ef,dt,t,ang,sum,xb,yb,x,y;
int i;
sta=start*rad; // 角度化为弧度
ef = efe*rad;
bf=bfe*rad;
dt=(ef-bf)/(float)inc; //dt为分段间隔角
t=sta+bf;
xb=x0+a*cos(t)+(a-b)*sin(bf)*sin(sta);
//计算起点坐标
yb=yo+a*sin(t)-(a-b)*sin(bf)*cos(sta);
move(xb,yb); //移到起点处
for (i=1;i<=inc;i++)
{kk1}
ang=bf+i*dt;
sum=sta+ang; //计算椭圆上动点的坐标
x=xo+a*cos(sum)+(a-b)*sin(ang)*sin(sta);
y=yo+a*sin(sum)-(a-b)*sin(ang)*cos(sta);
drawto(x,y); }} //分段画椭圆
当start=0度时,画水平椭圆;
当start=60度时,画正面椭圆;
当start=120度时,画侧面椭圆;

这一章里,我们以C语言三维图形设计为主,介绍了三维图形设计的基本概念,各种造型技巧以及C语言实现方法。由于三维图形在转换过程中要用到大量的数学模型记公式,我们可以看到用编程的方法实现起来,并不是一件易事。虽然,在建立了完备的图形程序库后,对于一个实体建模,可以用很少的语句在主函数中实现。但构造程序库的工作量也不小,而且,若要实现更复杂的图形处理,就需要增加程序库中的函数。可见,用编程来建立三维世界,不但费时费力,也极不方便。
随着计算机图形技术的发展,三维图形处理软件越来越多,功能也越来越强。通过这些可视化软件,我们可以很方便的做出各种各样的三维物体,并对其随心所欲的进行移动、旋转、变形。你也可以把更多的时间花在图形效果的处理,制做出精美的三维立体图像。在下一章里,我们就会看到如何用这些软件制作动人的计算机艺术画像。

                                                     

 

 
阅读(8599) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~