5.6.2 输出软件

下面我们考虑输出软件。首先我们将讨论到文本窗口的简单输出,这是程序员通常喜欢使用的方式。然后,我们将考虑图形用户界面,这是其他用户经常喜欢使用的。

1.文本窗口

当输出是连续的单一字体、大小和颜色的形式时,输出比输入简单。大体上,程序将字符发送到当前窗口,而字符在那里显示出来。通常,一个字符块或者一行是在一个系统调用中被写到窗口上的。

广告:个人专属 VPN,独立 IP,无限流量,多机房切换,还可以屏蔽广告和恶意软件,每月最低仅 5 美元

屏幕编辑器和许多其他复杂的程序需要能够以更加复杂的方式更新屏幕,例如在屏幕的中间替换一行。为满足这样的需要,大多数输出驱动程序支持一系列命令来移动光标,在光标处插入或者删除字符或行。这些命令常常被称为转义序列(escape sequence)。在25行80列ASCII哑终端的全盛期,有数百种终端类型,每一种都有自己的转义序列。因而,编写在一种以上的终端类型上工作的软件是十分困难的。

一种解决方案是称为termcap的终端数据库,它是在伯克利UNIX中引入的。该软件包定义了许多基本动作,例如将光标移动到(行,列)。为了将光标移动到一个特殊的位置,软件(如一个编辑器)使用一个一般的转义序列,然后该转义序列被转换成将要被执行写操作的终端的实际转义序列。以这种方式,该编辑器就可以工作在任何具有termcap数据库入口的终端上。许多UNIX软件仍然以这种方式工作,即使在个人计算机上。

逐渐地,业界看到了转义序列标准化的需要,所以就开发了一个ANSI标准。图5-36所示为一些该标准的取值。

阅读 ‧ 电子书库
图 5-36 终端驱动程序在输出时接受的ANSI转义序列。ESC表示ASCII转义字符(0x1B),n、m和s是可选的数值参数

下面考虑文本编辑器怎样使用这些转义序列。假设用户键入了一条命令指示编辑器完全删除第3行,然后封闭第2行和第4行之间的间隙。编辑器可以通过串行线向终端发送如下的转义序列:


ESC [3;1 H ESC [0 K ESC [1 M


(其中在上面使用的空格只是为了分开符号,它们并不传送)。这一序列将光标移动到第3行的开头,擦除整个一行,然后删除现在的空行,使从第4行开始的所有行向上移动一行。现在,第4行变成了第3行,第5行变成了第4行,以此类推。类似的转义序列可以用来在显示器的中间添加文本。字和字符可以以类似的方式添加或删除。

2.X窗口系统

几乎所有UNIX系统的用户界面都以X窗口系统(X Window System)为基础,X窗口系统经常仅称为X,它是作为Athena计划 [1] 的一部分于20世纪80年代在MIT开发的。X窗口系统具有非常好的可移植性,并且完全运行在用户空间中。人们最初打算将其用于将大量的远程用户终端与中央计算服务器相连接,所以它在逻辑上分成客户软件和主机软件,这样就有可能运行在不同的计算机上。在现代个人计算机上,两部分可以运行在相同的机器上。在Linux系统上,流行的Gnome和KDE桌面环境就运行在X之上。

当X在一台机器上运行时,从键盘或鼠标采集输入并且将输出写到屏幕上的软件称为X服务器(X server)。它必须跟踪当前选择了哪个窗口(鼠标指针所在处),这样它就知道将新的键盘输入发送给哪个客户。它与称为X客户(X client)的运行中的程序进行通信(可能通过网络)。它将键盘与鼠标输入发送给X客户,并且从X客户接收显示命令。

X服务器总是位于用户的计算机内部,而X客户有可能在远方的远程计算服务器上,这看起来也许有些不可思议,但是X服务器的主要工作是在屏幕上显示位,所以让它靠近用户是有道理的。从程序的观点来看,它是一个客户,吩咐服务器做事情,例如显示文本和几何图形。服务器(在本地PC中)只是做客户吩咐它做的事情,就像所有服务器所做的那样。

对于X客户和X服务器在不同机器上的情形,客户与服务器的布置如图5-37所示。但是当在单一的机器上运行Gnome或者KDE时,客户只是使用X库与相同机器上的X服务器进行会话的某些应用程序(但是通过套接字使用TCP连接,与远程情形中所做的工作相同)。

阅读 ‧ 电子书库
图 5-37 MIT X窗口系统中的客户与服务器

在单机上或者通过网络在UNIX(或其他操作系统)之上运行X窗口系统都是可行的,其原因在于X实际上定义的是X客户与X服务器之间的X协议,如图5-37所示。客户与服务器是在同一台机器上,还是通过一个局域网隔开了100m,或者是相距几千公里并且通过Internet相连接都无关紧要。在所有这些情况下,协议与系统操作都是完全相同的。

X只是一个窗口系统,它不是一个完全的GUI。为了获得完全的GUI,要在其上运行其他软件层。一层是Xlib,它是一组库过程,用于访问X的功能。这些过程形成了X窗口系统的基础,我们将在下面对其进行分析,但是这些过程过于原始了,以至于大多数用户程序不能直接访问它们。例如,每次鼠标点击是单独报告的,所以确定两次点击实际上形成了双击必须在Xlib之上处理。

为了使得对X的编程更加容易,作为X的一部分提供了一个工具包,组成了Intrinsics(本征函数集)。这一层管理按钮、滚动条以及其他称为窗口小部件(widget)的GUI元素。为了产生真正的GUI界面,具有一致的外观与感觉,还需要另外一层软件(或者几层软件)。一个例子是Motif,如图5-37所示,它是Solaris和其他商业UNIX系统上使用的公共桌面环境(Common Desktop Environment)的基础。大多数应用程序利用的是对Motif的调用,而不是对Xlib的调用。Gnome和KDE具有与图5-37相类似的结构,只是库有所不同。Gnome使用GTK+库,KDE使用Qt库。拥有两个GUI是否比一个好是有争议的。

此外,值得注意的是窗口管理并不是X本身的组成部分。将其遗漏的决策完全是故意的。一个单独的客户进程,称为窗口管理器(window manager),控制着屏幕上窗口的创建、删除以及移动。为了管理窗口,窗口管理器要发送命令到X服务器告诉它做什么。窗口管理器经常运行在与X客户相同的机器上,但是理论上它可以运行在任何地方。

这一模块化设计,包括若干层和多个程序,使得X高度可移植和高度灵活。它已经被移植到UNIX的大多数版本上,包括Solaris、BSD的所有派生版本、AIX、Linux等,这就使得对于应用程序开发人员来说在多种平台上拥有标准的用户界面成为可能。它还被移植到其他操作系统上。相反,在Windows中,窗口与GUI系统在GDI中混合在一起并且处于内核之中,这使得它们维护起来十分困难,并且当然是不可移植的。

现在让我们像是从Xlib层观察那样来简略地看一看X。当一个X程序启动时,它打开一个到一个或多个X服务器(我们称它们为工作站)的连接,即使它们可能与X程序在同一台机器上。在消息丢失与重复由网络软件来处理的意义上,X认为这一连接是可靠的,并且它不用担心通信错误。通常在服务器与客户之间使用的是TCP/IP。

四种类型的消息通过连接传递:

1)从程序到工作站的绘图命令。

2)工作站对程序请求的应答。

3)键盘、鼠标以及其他事件的通告。

4)错误消息。

从程序到工作站的大多数绘图命令是作为单向消息发送的,不期望应答。这样设计的原因是当客户与服务器进程在不同的机器上时,命令到达服务器并且执行要花费相当长的时间周期。在这一时间内阻塞应用程序将不必要地降低其执行速度。另一方面,当程序需要来自工作站的信息时,它只好等待直到应答返回。

与Windows类似,X是高度事件驱动的。事件从工作站流向程序,通常是为响应人的某些行动,例如键盘敲击、鼠标移动或者一个窗口被显现。每个事件消息32个字节,第一个字节给出事件类型,下面的31个字节提供附加的信息。存在许多种类的事件,但是发送给一个程序的只有那些它宣称愿意处理的事件。例如,如果一个程序不想得知键释放的消息,那么键释放的任何事件都不会发送给它。与在Windows中一样,事件是排成队列的,程序从队列中读取事件。然而,与Windows不同的是,操作系统绝对不会主动调用在应用程序之内的过程,它甚至不知道哪个过程处理哪个事件。

X中的一个关键概念是资源(resource)。资源是一个保存一定信息的数据结构。应用程序在工作站上创建资源。在工作站上,资源可以在多个进程之间共享。资源的存活期往往很短,并且当工作站重新启动后资源不会继续存在。典型的资源包括窗口、字体、颜色映射(调色板)、像素映射(位图)、光标以及图形上下文。图形上下文用于将属性与窗口关联起来,在概念上与Windows的设备上下文相类似。

X程序的一个粗略的、不完全的框架如图5-38所示。它以包含某些必需的头文件开始,之后声明某些变量。然后,它与X服务器连接,X服务器是作为XOpenDisplay的参数设定的。接着,它分配一个窗口资源并且将指向该窗口资源的句柄存放在win中。实际上,一些初始化应该出现在这里,在初始化之后X程序通知窗口管理器新窗口的存在,因而窗口管理器能够管理它。

阅读 ‧ 电子书库
图 5-38 X窗口应用程序的框架

对XCreateGC的调用创建一个图形上下文,窗口的属性就存放在图形上下文中。在一个更加复杂的程序中,窗口的属性应该在这里被初始化。下一条语句对XSelectInput的调用通知X服务器程序准备处理哪些事件,在本例中,程序对鼠标点击、键盘敲击以及窗口被显现感兴趣。实际上,一个真正的程序还会对其他事件感兴趣。最后,对XMapRaised的调用将新窗口作为最顶层的窗口映射到屏幕上。此时,窗口在屏幕上成为可见的。

主循环由两条语句构成,并且在逻辑上比Windows中对应的循环要简单得多。此处,第一条语句获得一个事件,第二条语句对事件类型进行分派从而进行处理。当某个事件表明程序已经结束的时候,running被设置为0,循环结束。在退出之前,程序释放了图形上下文、窗口和连接。

值得一提的是,并非每个人都喜欢GUI。许多程序员更喜欢上面5.6.2节讨论的那种传统的面向命令行的界面。X通过一个称为xterm的客户程序解决了这一问题。该程序仿真了一台古老的VT102智能终端,完全具有所有的转义序列。因此,编辑器(例如vi和emacs)以及其他使用termcap的软件无需修改就可以在这些窗口中工作。

3.图形用户界面

大多数个人计算机提供了GUI(Graphical User Interface,图形用户界面)。首字母缩写词GUI的发音是“gooey”。

GUI是由斯坦福研究院的Douglas Engelbart和他的研究小组发明的。之后GUI被Xerox PARC的研究人员摹仿。在一个风和日丽的日子,Apple公司的共同创立者Steve Jobs参观了PARC,并且在一台Xerox计算机上见到了GUI。这使他产生了开发一种新型计算机的想法,这种新型计算机就是Apple Lisa。Lisa因为太过昂贵因而在商业上是失败的,但是它的后继者Macintosh获得了巨大的成功。

当Microsoft得到Macintosh的原型从而能够在其上开发Microsoft Office时,Microsoft请求Apple发放界面许可给所有新来者,这样Macintosh就能够成为新的业界标准。(Microsoft从Office获得了比MS-DOS多得多的收入,所以它愿意放弃MS-DOS以获得更好的平台用于Office。)Apple负责Macintosh的主管Jean-Louis Gassée拒绝了Microsoft的请求,并且Steve Jobs已经离开了Apple而不能否决他。最终,Microsoft得到了界面要素的许可证,这形成了Windows的基础。当Microsoft开始追上Apple时,Apple提起了对Microsoft的诉讼,声称Microsoft超出了许可证的界限,但是法官并不认可,并且Windows继续追赶并超过了Macintosh。如果Gassée同意Apple内部许多人的看法(他们也希望将Macintosh软件许可给任何人),那么Apple或许会因为许可费而变得无限富有,并且现在就不会存在Windows了。

GUI具有用字符WIMP表示的四个基本要素,这些字母分别代表窗口(Window)、图标(Icon)、菜单(Menu)和定点设备(Pointing device)。窗口是一个矩形块状的屏幕区域,用来运行程序。图标是小符号,可以在其上点击导致某个动作发生。菜单是动作列表,人们可以从中进行选择。最后,定点设备是鼠标、跟踪球或者其他硬件设备,用来在屏幕上移动光标以便选择项目。

GUI软件可以在用户级代码中实现(如UNIX系统所做的那样),也可以在操作系统中实现(如Windows的情况)。

GUI系统的输入仍然使用键盘和鼠标,但是输出几乎总是送往特殊的硬件电路板,称为图形适配器(graphics adapter)。图形适配器包含特殊的内存,称为视频RAM(video RAM),它保存出现在屏幕上的图像。高端的图形适配器通常具有强大的32位或64位CPU和多达1GB自己的RAM,独立于计算机的主存。

每个图形适配器支持几种屏幕尺寸。常见的尺寸是1024×768、1280×960、1600×1200和1920×1200。除了1920×1200以外,所有这些尺寸的宽高比都是4:3,符合NTSC和PAL电视机的屏幕宽高比,因此可以在用于电视机的相同的监视器上产生正方形的像素。1920×1200尺寸意在用于宽屏监视器,它的宽高比与这一分辨率相匹配。在最高的分辨率下,每个像素具有24位的彩色显示,只是保存图像就需要大约6.5MB的RAM,所以,拥有256MB或更多的RAM,图形适配器就能够一次保存许多图像。如果整个屏幕每秒刷新75次,那么视频RAM必须能够连续地以每秒489MB的速率发送数据。

GUI的输出软件是一个巨大的主题。单是关于Windows GUI就写下了许多1500多页的书(例如Petzold,1999;Simon,1997;Rector和Newcomer,1997)。显然,在这一小节中,我们只可能浅尝其表面并且介绍少许基本的概念。为了使讨论具体化,我们将描述Win32 API,它被Windows的所有32位版本所支特。在一般意义上,其他GUI的输出软件大体上是相似的,但是细节迥然不同。

屏幕上的基本项目是一个矩形区域,称为窗口(window)。窗口的位置和大小通过给定两个斜对角的坐标(以像素为单位)惟一地决定。窗口可以包含一个标题条、一个菜单条、一个工具条、一个垂直滚动条和一个水平滚动条。典型的窗口如图5-39所示。注意,Windows的坐标系将原点置于左上角并且y向下增长,这不同于数学中使用的笛卡儿坐标。

阅读 ‧ 电子书库
图 5-39 XGA显示器上位于(200,100)处的一个窗口样例

当窗口被创建时,有一些参数可以设定窗口是否可以被用户移动,是否可以被用户调整大小,或者是否可以被用户滚动(通过拖动滚动条上的拇指)。大多数程序产生的主窗口可以被移动、调整大小和滚动,这对于Windows程序的编写方式具有重大的意义。特别地,程序必须被告知关于其窗口大小的改变,并且必须准备在任何时刻重画其窗口的内容,即使在程序最不期望的时候。

因此,Windows程序是面向消息的。涉及键盘和鼠标的用户操作被Windows所捕获,并且转换成消息,送到正在被访问的窗口所属于的程序。每个程序都有一个消息队列,与程序的所有窗口相关的消息都被发送到该队列中。程序的主循环包括提取下一条消息,并且通过调用针对该消息类型的内部过程对其进行处理。在某些情况下,Windows本身可以绕过消息队列而直接调用这些过程。这一模型与UNIX的过程化代码模型完全不同,UNIX模型是提请系统调用与操作系统相互作用的。然而,X是面向事件的。

为了使这一编程模型更加清晰,请考虑图5-40的例子。在这里我们看到的是Windows主程序的框架,它并不完整并且没有做错误检查,但是对于我们的意图而言它显示了足够的细节。程序的开头包含一个头文件windows.h,它包含许多宏、数据类型、常数、函数原型,以及Windows程序所需要的其他信息。

阅读 ‧ 电子书库
图 5-40 Windows主程序的框架

主程序以一个声明开始,该声明给出了它的名字和参数。WINAPI宏是一条给编译器的指令,让编译器使用一定的参数传递约定并且不需要我们进一步关心。第一个参数h是一个实例句柄,用来向系统的其他部分标识程序。在某种程度上,Win32是面向对象的,这意味着系统包含对象(例如程序、文件和窗口)。对象具有状态和相关的代码,而相关的代码称为方法(method),它对于状态进行操作。对象是使用句柄来引用的,在该示例中,h标识的是程序。第二个参数只是为了向后兼容才出现的,它已不再使用。第三个参数szCmd是一个以零终止的字符串,包含启动该程序的命令行,即使程序不是从命令行启动的。第四个参数iCmdShow表明程序的初始窗口应该占据整个屏幕,占据屏幕的一部分,还是一点也不占据屏幕(只是任务条)。

该声明说明了一个广泛采用的Microsoft约定,称为匈牙利记号(Hungarian notation)。该名称是一个涉及波兰记号的双关语,波兰记号是波兰逻辑学家J.Lukasiewicz发明的后缀系统,用于不使用优先级和括号表示代数公式。匈牙利记号是Microsoft的一名匈牙利程序员Charles Simonyi发明的,它使用标识符的前几个字符来指定类型。允许的字母和类型包括c(character,字符)、w(word,字,现在意指无符号16位整数)、i(integer,32位有符号整数)、l(long,也是一个32位有符号整数)、s(string,字符串)、sz(string terminated by a zero byte,以零字节终止的字符串)、p(pointer,指针)、fn(function,函数)和h(handle,句柄)。因此,举例来说,szCmd是一个以零终止的字符串并且iCmdShow是一个整数。许多程序员认为在变量名中像这样对类型进行编码没有什么价值,并且使Windows代码异常地难于阅读。在UNIX中就没有类似这样的约定。

每个窗口必须具有一个相关联的类对象定义其属性,在图5-40中,类对象是wndclass。对象类型WNDCLASS具有10个字段,其中4个字段在图5-40中被初始化,在一个以实际的程序中,其他6个字段也要被初始化。最重要的字段是lpfnWndProc,它是一个指向函数的长(即32位)指针,该函数处理引向该窗口的消息。此处被初始化的其他字段指出在标题条中使用哪个名字和图标,以及对于鼠标光标使用哪个符号。

在wndclass被初始化之后,RegisterClass被调用,将其发送给Windows。特别地,在该调用之后Windows就会知道当各种事件发生时要调用哪个过程。下一个调用CreateWindow为窗口的数据结构分配内存并且返回一个句柄以便以后引用它。然后,程序做了另外两个调用,将窗口轮廓置于屏幕之上,并且最终完全地填充窗口。

此刻我们到达了程序的主循环,它包括获取消息,对消息做一定的转换,然后将其传回Windows以便让Windows调用WndProc来处理它。要回答这一完整的机制是否能够得到化简的问题,答案是肯定的,但是这样做是由于历史的缘故,并且我们现在坚持这样做。

主循环之后是过程WndProc,它处理发送给窗口的各种消息。此处CALLBACK的使用与上面的WINAPI相类似,为参数指明要使用的调用序列。第一个参数是要使用的窗口的句柄。第二个参数是消息类型。第三和第四个参数可以用来在需要的时候提供附加的信息。

消息类型WM_CREATE和WM_DESTROY分别在程序的开始和结束时发送。它们给程序机会为数据结构分配内存,并且将其返回。

第三个消息类型WM_PAINT是一条指令,让程序填充窗口。它不仅当窗口第一次绘制时被调用,而且在程序执行期间也经常被调用。与基于文本的系统相反,在Windows中程序不能够假定它在屏幕上画的东西将一直保持在那里直到将其删除。其他窗口可能会被拖拉到该窗口的上面,菜单可能会在窗口上被拉下,对话框和工具提示可能会覆盖窗口的某一部分,如此等等。当这些项目被移开后,窗口必须重绘。Windows告知一个程序重绘窗口的方法是发送WM_PAINT消息。作为一种友好的姿态,它还会提供窗口的哪一部分曾经被覆盖的信息,这样程序就更加容易重新生成窗口的那一部分而不必重绘整个窗口。

Windows有两种方法可以让一个程序做某些事情。一种方法是投递一条消息到其消息队列。这种方法用于键盘输入、鼠标输入以及定时器到时。另一种方法是发送一条消息到窗口,从而使Windows直接调用WndProc本身。这一方法用于所有其他事件。由于当一条消息完全被处理后Windows会得到通报,这样Windows就能够避免在前一个调用完成前产生新的调用,由此可以避免竞争条件。

还有许多其他消息类型。当一个不期望的消息到达时为了避免异常行为,最好在WndProc的结尾处调用DefWindowProc,让默认处理过程处理其他情形。

总之,Windows程序通常创建一个或多个窗口,每个窗口具有一个类对象。与每个程序相关联的是一个消息队列和一组处理过程。最终,程序的行为由到来的事件驱动,这些事件由处理过程来处理。与UNIX采用的过程化观点相比,这是一个完全不同的世界观模型。

对屏幕的实际绘图是由包含几百个过程的程序包处理的,这些过程捆在一起形成了GDI(Graphics Device Interface,图形设备接口)。它能够处理文本和各种类型的图形,并且被设计成与平台和设备无关的。在一个程序可以在窗口中绘图(即绘画)之前,它需要获取一个设备上下文(device context):设备上下文是一个内部数据结构,包含窗口的属性,诸如当前字体、文本颜色、背景颜色等。大多数GDI调用使用设备上下文,不管是为了绘图,还是为了获取或设置属性。

有许许多多的方法可用来获取设备上下文。下面是一个获取并使用设备上下文的简单例子:


hdc=GetDC(hwnd);

TextOut(hdc,x,y,psText,iLength);

ReleaseDC(hwnd,hdc);


第一条语句获取一个设备上下文的句柄hdc。第二条语句使用设备上下文在屏幕上写一行文本,该语句设定了字符串开始处的(x,y)坐标、一个指向字符串本身的指针以及字符串的长度。第三个调用释放设备上下文,表明程序在当时已通过了绘图操作。注意,hdc的使用方式与UNIX的文件描述符相类似。还需要注意的是,ReleaseDC包含冗余的信息(使用hdc就可以惟一地指定一个窗口)。使用不具有实际价值的冗余信息在Windows中是很常见的。

另一个有趣的注意事项是,当hdc以这样的方式被获取时,程序只能够写窗口的客户区,而不能写标题条和窗口的其他部分。在内部,在设备上下文的数据结构中,维护着一个修剪区域。在修剪区域之外的任何绘图操作都将被忽略。然而,存在着另一种获取设备上下文的方法GetWindowDC,它将修剪区域设置为整个窗口。其余的调用以其他的方法限定修剪区域。拥有多种调用做几乎相同的事情是Windows的另一个特性。

GDI的完全论述超出了这里讨论的范围。对于感兴趣的读者,上面引用的参考文献提供了补充的信息。然而,关于GDI可能还值得再说几句话,因为GDI是如此之重要。GDI具有各种各样的过程调用以获取和释放设备上下文,获取关于设备上下文的信息,获取和设置设备上下文的属性(例如背景颜色),使用GDI对象(例如画笔、画刷和字体,其中每个对象都有自己的属性)。最后,当然存在许多实际在屏幕上绘图的GDI调用。

绘图过程分成四种类型:绘制直线和曲线、绘制填充区域、管理位图以及显示文本。我们在上面看到了绘制文本的例子,所以让我们快速地看看其他类型之一。调用


Rectangle(hdc,xleft,ytop,xright,ybottom);


将绘制一个填充的矩形,它的左上角和右下角分别是(xleft,ytop)和(xright,ybottom)。例如


Rectangle(hdc,2,1,6,4);


将绘制一个如图5-41所示的矩形。线宽和颜色以及填充颜色取自设备上下文。其他的GDI调用在形式上是类似的。

阅读 ‧ 电子书库
图 5-41 使用Rectangle绘制矩形的例子。每个方框代表一个像素

4.位图

GDI过程是矢量图形学的实例。它们用于在屏幕上放置几何图形和文本。它们能够十分容易地缩放到较大和较小的屏幕(如果屏幕上的像素数是相同的)。它们还是相对设备无关的。一组对GDI过程的调用可以聚集在一个文件中,描述一个复杂的图画。这样的文件称为Windows元文件(metafile),广泛地用于从一个Windows程序到另一个Windows程序传送图画。这样的文件具有扩展名.wmf。

许多Windows程序允许用户复制图画(或一部分)并且放在Windows的剪贴板上,然后用户可以转入另一个程序并且粘贴剪贴板的内容到另一个文档中。做这件事的一种方法是由第一个程序将图画表示为Windows元文件并且将其以.wmf格式放在剪贴板上。此外,还有其他的方法做这件事。

并不是计算机处理的所有图像都能够使用矢量图形学来生成。例如,照片和视频就不使用矢量图形学。反之,这些项目可以通过在图像上覆盖一层网格扫描输入。每一个网格方块的平均红、绿、蓝取值被采样并且保存为一个像素的值。这样的文件称为位图(bitmap)。Windows中有大量的工具用于处理位图。

位图的另一个用途是用于文本。在某种字体中表示一个特殊字符的一种方法是将其表示为小的位图。于是往屏幕上添加文本就变成移动位图的事情。

使用位图的一种一般方法是通过调用BitBlt过程,该过程调用如下:


BitBIt(dsthdc,dx,dy,wid,ht,srchdc,sx,sy,rasterop);


在其最简单的形式中,该过程从一个窗口中的一个矩形复制位图到另一个窗口(或同一个窗口)的一个矩形中。前三个参数设定目标窗口和位置,然后是宽度和高度,接下来是源窗口和位置。注意,每个窗口都有其自己的坐标系,(0,0)在窗口的左上角处。最后一个参数将在下面描述。


BitBlt(hdc2,1,2,5,7,hdcl,2,2,SRCCOPY);


的效果如图5-42所示。注意字母A的整个5×7区域被复制了,包括背景颜色。

除了复制位图外,BitBlt还可以做很多事情。最后一个参数提供了执行布尔运算的可能,从而可以将源位图与目标位图合并在一起。例如,源位图可以与目标位图执行或运算,从而融入目标位图;源位图还可以与目标位图执行异或运算,该运算保持了源位图和目标位图的特征。

位图具有的一个问题是它们不能缩放。8×12方框内的一个字符在640×480的显示器上看起来是适度的。然而,如果该位图以每英寸1200点复制到10 200位×13 200位的打印页面上,那么字符宽度(8像素)为8/1200英寸或0.17mm。此外,在具有不同彩色属性的设备之间进行复制,或者在单色设备与彩色设备之间进行复制效果并不理想。

阅读 ‧ 电子书库
图 5-42 使用BitBlt复制位图:a)复制前;b)复制后

由于这样的缘故,Windows还支持一个称为DIB(Device Independent Bitmap,设备无关的位图)的数据结构。采用这种格式的文件使用扩展名.bmp。这些文件在像素之前具有文件与信息头以及一个颜色表,这样的信息使得在不同的设备之间移动位图十分容易。

5.字体

在Windows 3.1版之前的版本中,字符表示为位图,并且使用BitBlt复制到屏幕上或者打印机上。这样做的问题是,正如我们刚刚看到的,在屏幕上有意义的位图对于打印机来说太小了。此外,对于每一尺寸的每个字符,需要不同的位图。换句话说,给定字符A的10点阵字型的位图,没有办法计算它的12点阵字型。因为每种字体的每一个字符可能都需要从4点到120点范围内的各种尺寸,所以需要的位图的数目是巨大的。整个系统对于文本来说简直是太笨重了。

该问题的解决办法是TrueType字体的引入,TrueType字体不是位图而是字符的轮廓。每个TrueType字符是通过围绕其周界的一系列点来定义的,所有的点都是相对于(0,0)原点。使用这一系统,放大或者缩小字符是十分容易的,必须要做的全部事情只是将每个坐标乘以相同的比例因子。采用这种方法,TrueType字符可以放大或者缩小到任意的点阵尺寸,甚至是分数点阵尺寸。一旦给定了适当的尺寸,各个点可以使用幼儿园教的著名的逐点连算法连接起来(注意现代幼儿园为了更加光滑的结果而使用曲线尺)。轮廓完成之后,就可以填充字符了。图5-43给出了某些字符缩放到三种不同点阵尺寸的一个例子。

阅读 ‧ 电子书库
图 5-43 不同点阵尺寸的字符轮廓的一些例子

一旦填充的字符在数学形式上是可用的,就可以对它进行栅格化,也就是说,以任何期望的分辨率将其转换成位图。通过首先缩放然后栅格化,我们可以肯定显示在屏幕上的字符与出现在打印机上的字符将是尽可能接近的,差别只在于量化误差。为了进一步改进质量,可以在每个字符中嵌入表明如何进行栅格化的线索。例如,字母T顶端的两个衬线应该是完全相同的,否则由于舍入误差可能就不是这样的情况了。

[1] Athena(雅典娜)指麻省理工学院(MIT)校园范围内基于UNIX的计算环境。——译者注