第七章 颜色
这一章说明如何在程序中使用颜色。在 X
中颜色的处理可能比在其它系统更加复杂,因为需要针对许多不同类型显示(display)的可移植性。颜色处理中的一些先进的主题在 X
标准中规定的仍然比较差。这一章从每个人都应该读的基础开始,并且逐渐地转移到更先进的主题,包括 R5
的设备无关的颜色。从最新的部分中适当地摘录和选择了一些。
典型的 X
应用程序允许用户指定窗口的背景和边框,指定光标的颜色,以及为绘制文本和图形而在 DC
中设置的前景和背景颜色。更复杂的应用(例如计算机辅助设计(CAD)应用)可能用颜色来区分物理和逻辑层次。更加复杂的应用程序,如在图像处理中,可以使用颜色的粒度分级来表示真实世界的数据。然而在背景和边框窗口属性和如何设置
GC 的前景和背景成员的讨论中,我们只讲过像素值。
如何将这些像素转化成颜色?并且如果 X 客户程序在 X
环境的多种多样的屏幕硬件中成功地运行,它必须要如何管理颜色?
因为 X 必须支持拥有不同屏幕硬件的多样性系统,Xlib
颜色处理机制是相当复杂的。即使以前写过颜色图形应用程序的程序员也将会发现有一些新的概念要学习。
本章从说明 X 应用程序可能运行的不同屏幕类型和 Xlib
为判断屏幕类型提供的机制出发。然后说明某些原则上使用颜色作为装饰的应用程序会用到的最简单的颜色分配机制。进入讨论更加复杂颜色应用程序并且以编写在不同颜色类型和单色屏幕之间可移植的应用程序作为总结。
基本颜色术语和概念
今天市场上大多数彩色屏幕都是基于 RGB
颜色模式。屏幕上的每一个像素实际上是由三种萤光混合而成:红、绿和蓝。三个萤光的每一个对一个单独的电子波来感光。当所有的三个萤光全部亮的时候,在人眼看起来像素是白色。当所有的三个萤光都暗的时候,像素看起来是黑色的。当每一个原色的亮度变化的时候,三个萤光生成一种可能看起来很奇怪的另一种颜色。例如,红和绿有相同的比例,没有混合蓝,就产生了黄色。大多数人都熟悉用在绘画中的减色混合,在此红、黄和蓝是创造所有其它颜色(除了白色和灰度梯级)的三原色。
毫无疑问,你知道彩色屏幕使用每个像素多位(也称为多平面)来指定颜色。颜色映射(colormap)用来将每一个像素的值翻译成你可以在屏幕上看到的颜色。
一个颜色映射并不比存储在服务器上的查找表多。任何一个给定的像素值都用作对这个表的索引——例如,像素值 16
将选择第十六个元素,或者颜色元(colorcell)。
在最通用的颜色系统上,每一个颜色元对这三原色中的每一个都包含单独的 16 位亮度值。
如在图 7-1
中所示,像素值唯一地标识一个特定的颜色元。在窗口的可见部分中的每一个像素都连续地读出屏幕内存并且在颜色映射中进行查找。在指定颜色元中的
RGB 值控制三原色的亮度,并因此决定这个颜色显示在屏幕的那个点上。
在屏幕上可能的颜色范围是在用于 RGB
规范的颜色映射中有效位数的函数。如果对于每一个原色有八位有效的话,那么可能的颜色范围是 256[3](大约 1 亿 6
千万种颜色)。
不过,在任何某个时刻显示在屏幕上的不同颜色的数目是平面数的函数。一个 4 平面系统可能索引 2[4] 个颜色元(16
种不同的颜色);一个 8 平面的系统可能索引 2[8] 个颜色元(256 种不同的颜色);以及一个 24 平面系统可能索引 2[24]
个颜色元(超过 1 亿 6 千万种颜色)。
尝试使用颜色的客户程序并不指定像素值并且将颜色放入元中来绘出给定的颜色。取而代之的是,它请求存取颜色映射(由服务器管理)中的颜色元并且返回一个像素值。这称为分配一个颜色。当客户程序分配颜色时,它就问询服务器,“我可以用哪个颜色元?”并且服务器会回应,“你可以使用由这个像素值指定的颜色元。”有三个基本的函数来分配颜色,它们在本章稍后的部分详细说明并论证。
颜色命名和规范
下面的部分说明你想指定什么颜色的几种不同的方法。在只使用颜色作为装饰的程序中,程序员简单地选择缺省的颜色,并且允许用户用资源覆盖它们。
服务器端的颜色名称数据库
为了指定颜色规范并且改善颜色的共享,X
服务器提供将字符串颜色名称翻译成 RGB 值的颜色数据库。主要是为了用户方便,因为指定“黄色”比计算出黄色的 RGB
值要容易。但是它也鼓励颜色元的共享。如上所述,颜色元的共享可能是发生在两个客户程序用严格相同的 RGB
值来分配一个只读的颜色元的情况下。如果这两个客户程序通过指定 300 多个字符串名称中的一个来分配颜色的话,选择严格相同的 RGB
值比使用 2[48] 可能的 RGB 值的组合之一会更好。
因为屏幕硬件的差异,相同的 RGB
值可能在不同的硬件上生成完全不同的颜色。因此,服务器的实现者打算改变 RGB
值对应每一个颜色名称来确使在他们的屏幕上出现合适的颜色。这称为图像校正(gamma
correction)。通过使用来自这个数据库的名称,你可以确保得到接近于你请求的颜色。如果服务器的实现者并没有提供图像校正颜色数据库的话,程序无法说出要显示什么颜色,尽管它知道
RGB 值。这个问题用 X 颜色管理系统,或者说 Xcms 来解决,它是在版本 5 中引入的。Xcms
提供一个客户端的颜色数据库,并且支持设备无关的颜色规范。在 R5
中,颜色名称字符串首先用客户端的颜色数据库来查找,并且如果找不到,随后在服务器数据库上查找。
有一点很重要,就是注意颜色名称并不是由 X11 协议或者
Xlib
来指定的。因此,服务器的实现者可能改变它们,但是更常见的是,它们将简单地增加到这个表中。(注意,一些服务器允许用户来定制这个文件。更多的信息,参见第三卷。)
显示硬件的差异
中间范围彩色显示
单色和灰度级别
高性能彩色显示
随着内存变得更加便宜并且应用程序更加先进,有 24
个平面的工作站变得更加通用。每个像素有 24
位,同时在屏幕上显示每一个可辨别的颜色是可能的。这使平滑的阴影以及其它使用大量小间隔颜色的应用程序成为可能。
这么多平面的问题是用于中间范围彩色屏幕的颜色映射风格将大得不太可能:它将包含超过 1 亿 6
千万个项。取而代之的是,每个像素有效位被打破成三个单独的颜色映射索引,一个用于一种原色,如图 7-3
所示。这种方法仍然允许生成所有范围的颜色,但是使颜色映射的载入工作更加易于处理。这个方案要求三原色颜色映射的每一个中只有 256
个项来在 24 平面系统上指定 1 亿 6 千万种颜色。
在高性能屏幕中,只读的颜色映射正像读/写的颜色映射那样有意义,因为几乎每一个想像的颜色可以是同时可用的。用只读的颜色映射,在用于选择颜色的像素值和实际生成的
RGB
值之间有一个固定的关系。这使创建希望直接计算像素值而不是必须计算颜色然后决定哪一个像素值表示这个颜色的应用程序成为可能,正如当颜色映射是读/写时一样必要。
实际上,大多数这类屏幕通过使用虚拟颜色映射来让你以这两种风格中的任何一种来使用颜色。可以有一个只读的虚拟颜色映射和一个读/写的颜色映射。不过,不像在中间范围彩色屏幕硬件中那样,多数高性能彩色系统有多个硬件颜色映射,以使两种虚拟颜色映射可以同是安装并使用。实际上,在许多这样的系统上,每一个窗口可以同时在硬件上安装它自己的虚拟颜色映射表。
X 如何用 Visual 说明颜色支持
Visual
说明在为了某个特定屏幕上使用而已经或者可以创建的虚拟颜色映射的特性。正如 Xlib 所使用的那样,visual
实际上是一个指向包含了关于使用某个特定屏幕方式信息的结构(类型为 Visual)的指针。在创建颜色映射或者窗口的时候必须指定
visual,并且在创建窗口中所使用的 visual 必须与创建用于那个窗口的颜色映射的 visual 相同。
大多数窗口继承它们的父
visual,并且窗口将常常共享根窗口的 visual,即众所周知的缺省 visual。缺省的 visual
自然地说明缺省的颜色映射。如果你用 XCreateSimpleWindow() 来创建你的所有窗口的话,你将使用缺省的 visual
和缺省的颜色映射。
Visual
结构有意做成不透明的;程序都假定不能存取它的内容。这使得 Xlib
实现者可以改变结构而不会破坏已有的客户程序。用于避免存取其成员的过程并不总是很麻烦的,而恰恰是从开始到由应用程序编写者使用。在这一点上,大多数程序员已经打破了这一规则。我们在此将只给你正确的方法,因为它只是在应用程序中加入了数行代码。
尽管更多的现有应用程序完全避免 visual,并且只使用
DefaultDepth() 或者 DisplayPlanes()
宏来试图判断屏幕是单色的还是彩色的。不过,这通常并不可行,因为它并没有在灰度级别和彩色屏幕之间作出区分(这两者都有多于一个的平面)。作出这一区分的唯一方法是获得关于
visual 的信息。
记住 visual
只是一种在某个特定屏幕上使用颜色的方式。可以用每一个描述颜色映射不同深度和可写性的 visual 来列出某个屏幕所支持的
visual。在一个彩色系统上,可能单色和彩色的 visual 都可用。
在某个特定的屏幕上获得关于 visual 的方法是使用
XMatchVisualInfo() 或者 XGetVisualInfo()。这些函数返回包含可用 visual
信息的 XVisualInfo 结构并且是公共的,以使它们的域可以被安全地存取。
XVisualInfo 的 class
成员包含一个指定对应使用屏幕基本方法的六种不同 visual
类:DirectColor、GrayScale、PseudoColor、StaticColor、StaticGray 或者
TrueColor 之一的常量。
如在下表中所总结的那样,visual
类在彩色和单色、颜色映射是读/写还是只读、以及像素值是否提供对颜色映射的索引或者是被分解成对应于红、绿和蓝值的单独索引之间作出了区分。
表 7-2. Visual 类的比较
颜色映射类型
读/写
只读
单色/灰度
GrayScale
StaticColor
对 RG&B 的单个索引
PseudoColor
StaticColor
对 RGB& 的分解索引
DirectColor
TrueColor
在某个特定的屏幕上,可能有多种使用颜色的方法,因此,可能有超过多种支持的
visual。这通常是对于高端工作站而言。如我们将在稍后所说明的,有多种方式搜索可用的 visual
来选择一种接近符合你的应用程序需求的一个。可能提供同一类的几个
visual,但有不同的深度。在高性能屏幕上,可能作为读写或者只读的方式来创建颜色映射。这两种方法都有一些优点并且应当用于不同的应用程序。对于使用屏幕硬件的这些方法中的每一种而言,都应当有一个单独的
visual。这些 visual 之一应当是 TrueColor 类和其它的 Directcolor 类。一些 24
平面的屏幕允许将屏幕视作两个单独的 12 平面的 PseudoColor visual。(这样的话允许“双缓冲”,一种对于动画或者在
3-D 应用程序中储存距离数据来简化隐藏线和平面计算很有用的技术。)事实上,在一些先进的工作站上,你可以在每一个窗口中使用一种不同的
visual。
图 7-4
示意性地表现了理论上可以由每种类型的屏幕硬件所支持的 visual 类。支持 DirectColor 类的屏幕理论上可以支持六种
visual 类的任何一种。支持 PseudoColor 类的屏幕可以支持
GrayScale、PseudoColor、StaticColor 或者 StaticGray visual 类。支持
GrayScale visual 类的屏幕也能支持 StaticGray visual
类。拥有只读颜色映射的三种类型的屏幕只能支持它们自己类的 visual。但是记住只是因为一些 visual
类理论上可能被一些屏幕硬件所支持并不意味着服务器实现者会决定支持这类 visual。
共享性和可变性
注意 DirectColor、GrayScale 和
PseudoColor visual 有可以改变的颜色映射,而 StaticColor、StaticGray 和 TrueColor
有不变的颜色映射。在可变的颜色映射中,可能有两种颜色元类型:只读的和读/写的。在只读元中的颜色一旦由客户程序设置,从那时起就可以由任何客户程序所共享而不会改变。读/写元可以在任何时刻由分配它的客户程序改变其颜色,但不能被其它客户程序所共享。在不变的颜色映射中,你被限制在只读元中。
不变颜色映射的一个优势就是所有的元都是只读的,并且可以在客户程序间共享,因此所有的元对于每一个客户程序都是可用的。不变的颜色映射也使从希望的颜色中计算像素值而不必询问服务器成为可能,因为像素值和颜色之间的映射是可预测的。这个技术对于平滑阴影和
3-D
渲染算法来说是必要的。如你会看到的那样,用可变的颜色映射通常是不可能实现这一点的。不变颜色映射的缺点是可能并没有你希望的准确颜色(如果只有很少的平面)并且你不能分配读/写元,因此你不能改变颜色元来改变屏幕上已有像素的颜色。要改变颜色,你必须用新像素值来重绘图形。
通常,可变颜色映射的优势是你可以拥有私有读/写元和共享只读元。这正是当屏幕支持 PseudoColor 和 DirectColor
时,它们为什么是最有用的 visual 的原因。PseudoColor 和 DirectColor
允许你决定你的客户程序是否真正需要读/写元或者它是否能够使用只读元。只读使用是首选,因为这些元可以由所有的客户程序所共享,这表示颜色映射不太可能会用完空闲的元。
要努力不将颜色映射的可写性与颜色元的可写性混淆。在读/写颜色映射中的颜色元可以被分配为读写的或者只读的。在只读颜色映射中的颜色元只能被分配成只读的。如果窗口管理器或者任何其它的客户程序只读地分配所有可用的颜色元的话,可变的颜色映射整个会成为只读的。
读/写颜色元只是在可变颜色映射中有优势,就是你的程序可以精确地选择你想要的颜色(只要在屏幕上具有物理上的可能)并且你可以随意改变颜色,如果颜色映射当前已经安装的话,这会立即改变用那个像素值绘制的任何东西的可视颜色。尽管其它的客户程序也可以改变读/写元中的值,只有分配这个元的客户程序可以改变其内容是一个约写俗成的东西。你拥有像素值。因为大多数客户并不满足对他们的显示颜色没有控制,这个像素值并不是可共享的。这意味着如果几个使用读/写元的客户程序正在运行,所有的颜色元可能都被使用。那么一些客户程序将被强制创建它们自己带有负面结果的颜色映射。
分配共享颜色
因为当客户程序存储私有颜色值时,空闲的颜色元可以快速地变成一个稀缺资源,主要将颜色用作装饰的简单客户程序被鼓励总是分配只读的颜色,因此这些颜色元可以由其它只读地分配相同颜色客户程序所共享。
返回的像素值可以用于设置窗口的
background_pixel 或者 border_pixel 属性或者设置由绘画请求所使用的 GC 的 foreground 或者
background 成员。
只读的颜色元可以用下列函数来分配:
XAllocColor() 返回包含请求的 RGB
值或者包含在屏幕上从物理上讲最接近的 RGB 值的颜色元索引。XcmsAllocColor() 是相同的,除了颜色是使用 Xcms
的语法来指定之外。
XAllocNamedColor() 返回包含与指定来自字符串颜色名称数据库的颜色名称相关的 RGB 值或者在屏幕上从物理上来说最接近的 RGB 值。在 R5
中,XAllocNamedColor() 接受 Xcms 语法的字符串。XcmsAllocNamedColor()
除了不同的参数风格外,几乎是同样的。XcmsAllocNamedColor()
也有能力返回一个描述在指定字符串中所发现格式的能力。
习惯上,客户程序允许用户在命令行中或者在资源数据库中使用颜色名称来指定颜色。当
RGB 值是通过指定颜色名称字符串来从颜色数据库中选取的时候,只读颜色元的共享很可能比如果颜色是作为纯 RGB
值或者使用十六进制规范指定的颜色要更多一些。
XParseColor()
分析颜色名称字符串或者十六进制颜色规范字符串并返回 RGB 值。它可以随 XAllocColor()
或者分配读/写元的函数一起使用,它们在稍后说明。对于颜色名称,它从服务器的颜色数据库中获得 RGB 值就像
XAllocNamedColor() 一样。你可能注意到 XAllocNamedColor() 非常类似于 XParseColor 和
XAllocColor() 的组合。
XColor 结构
XAllocColor() 和
XAllocNamedColor() (以及其它维护颜色元的函数)都接受一个 XColor 结构作为参数。这个结构用于指定希望的
RGB 值,以及返回像素值。
typedef struct {
unsigned long
pixel;
unsigned short red, green, blue;
char
flags;
char
pad;
} XColor;
XVisualInfo 结构
XVisualInfo 结构返回关于可用 visual
的信息。它既是可用于从这些可用的 visual 中选择一个 visual 类型又可以在使用某个特定 visual
时作为信息源。
typedef struct {
Visual *visual;
VisualID visualid;
int screen_num;
unsigned int depth;
int class;
unsigned long red_mask;
unsigned long green_mask;
unsigned long blue_mask;
int colormap_size;
int bits_per_rgb;
} XVisualInfo;
成员 visual 是一个指向内部 Visual
结构的指针。这个指针用作 XCreateWindow() 和 XCreateColormap() 的 visual 参数。