到目前为止,我们的绘制操作都只使用了黑白两色。现在我们就看看如何使用丰富的颜色来绘制。首先,是没有完全足够的颜色的。屏幕控制器同时只能支持有限的颜色。因此,一个应用程序不能只是要求使用颜色“轻紫红”就盼望这个颜色能被支持。每个应用分配它自己所需要的颜色,如果全部的16或256色入口都已经在使用了,下一个颜色的分配就会失败。
结果,就介绍使用“一个颜色映射”。一个颜色映射是一个与屏幕控制器同时可以支持的颜色数相同的表。每个表中的节点都为每种颜色包含一个RGB(红,绿和蓝)。当一个应用想在屏幕上绘制时,它并不指定使用什么颜色,而是指定使用映射表里那一个节点,因此,改变表里某个节点的值将会改变程序绘制的颜色。
为了能让程序员使用他想要的颜色来绘制,提供了颜色映射分配函数。你可以要求分配一个颜色映射节点来对应一个RGB值,然后一个节点的索引值返回给你。如果表满了,这个操作将会失败。你可以接下来要求一个相近的颜色来满足你的需要。这意味着一个相近的颜色将会被绘制到屏幕上去。
在当今的X 服务器使用的现代显示器上,一般都可以支持上百万的颜色,上面那个限制也许看起来挺傻的,但是记住还有很多古旧的显示卡和显示器在被使用。使用颜色映射,你可以不必考虑服务器的屏幕硬件细节就可以使用它们。在一个支持上百万的显示器上,任何颜色的分配请求都应该会成功。在一个职能支持有限颜色的显示器上可能会使用一个相近颜色来代替你的要求,这可能不好看,但你的程序仍然能工作。
分配和释放颜色映射
当你使用Xlib绘制的时候,你可以选择屏幕的标准颜色映射,或者为你的窗口分配一个新的颜色映射。在后一种情况下,每次鼠标移动到你的窗口上时,你窗口的颜色映射都将替换缺省的屏幕映射,然后你就会看到屏幕花一下其它的窗口颜色也改变了。实际上,这和你在使用“-install”选项运行X应用时效果一样。
系统定义了宏DefaultColormap来获取屏幕的标准颜色映射:
Colormap screen_colormap = DefaultColormap(display, DefaultScreen(display));
上面的调用将会返回第一个屏幕的缺省颜色映射的句柄(再多余的提醒一下,一个X服务器可以同时支持数个不同的屏幕,每个屏幕都可以有自己的资源)。
另一个选项,分配一个颜色映射,像下面这样工作:
/* first, find the default visual for our screen. */
Visual* default_visual = DefaultVisual(display, DefaultScreen(display));
/* this creates a new color map. the number of color entries in this map */
/* is determined by the number of colors supported on the given screen. */
Colormap my_colormap = XCreateColormap(display, win, default_visual, AllocNone);
注意,window参数是用来只允许X服务器为指定屏幕分配颜色映射。我们接下来可以使用分配来的颜色映射给同一个屏幕里的任意一个窗口使用。
分配和释放颜色节点
一旦我们获得了颜色映射的访问,我们就可以开始分配颜色。使用函数XAllocNameColor()和XAllocClor()来完成这个工作。首先函数 XAllocNameColor()获得颜色的名字(例如"红","蓝","棕"等等)然后获得能使用的实际相近颜色。函数XAllocColor()访问RGB颜色。两个函数都使用XColor结构,它有以下的一些数据域:
unsigned long pixel
颜色映射节点的索引。
unsigned short red
RGB颜色值的红色部分。
unsigned short green
RGB颜色值的绿色部分。
unsigned short blue
RGB颜色值的蓝色部分。
下面是使用的例子:
/* this structure will store the color data actually allocated for us. */
XColor system_color_1, system_color_2;
/* this structure will store the exact RGB values of the named color. */
/* it might be different from what was actually allocated. */
XColor exact_color;
/* allocate a "red" color map entry. */
Status rc = XAllocNamedColor(display, screen_colormap, "red", &system_color_1, &exact_color);
/* make sure the allocation succeeded. */
if (rc == 0) {
fprintf(stderr, "XAllocNamedColor - allocation of ''red'' color failed./n");}
else {
printf("Color entry for ''red'' - allocated as (%d,%d,%d) in RGB values./n", system_color_1.red, system_color_1.green, system_color_1.blue);}
/* allocate a color with values (30000, 10000, 0) in RGB. */
system_color_2.red = 30000;
system_color_2.green = 10000;
system_color_2.blue = 0;
Status rc = XAllocColor(display, screen_colormap, &system_color_2);
/* make sure the allocation succeeded. */
if (rc == 0) {
fprintf(stderr, "XAllocColor - allocation of (30000,10000,0) color failed./n");}
else {
/* do something with the allocated color... */ . .}
使用一个颜色绘制
我们在分配了希望的颜色之后,我们可以使用它们绘制文本或图形。为了达到目的,我们需要把获得的颜色设置给一些GC(图形上下文)作为前景色和背景色,然后使用设置好的GC来进行绘制。使用函数XSetForeground()和XSetBackground()来进行,如下:
/* use the previously defined colors as the foreground and background */
/* colors for drawing using the given GC. assume my_gc is a handle to */
/* a previously allocated GC. */
XSetForeground(display, my_gc, screen_color_1.pixel);
XSetForeground(display, my_gc, screen_color_2.pixel);
如你所见,这个是个使用的例子。实际的绘制工作使用我们以前介绍的绘图函数。注意,为了使用各种颜色完成绘制工作,我们可以使用两种方法。我们可以在调用绘图函数前改变GC的值,也可以使用代表不同颜色的GC。由你自己根据情况使用哪种方法。注意,使用多个GC降消耗X服务器额外的资源,但这样可以使你的代码显的更紧凑。
作为使用颜色绘制的例子,请察看例子程序color-drawing.c 。这是程序simple-drawing.c 的一个拷贝,我们只是添加了颜色的部分在里面。
X Bitmaps和Pixmaps
一个被称为多媒体的程序所有作的一件事情就是显示图片。在X的世界里,使用bitmaps和pixmaps来实现这个功能。在为我们的程序设置图标的介绍里我们已经使用了它们。现在让我们进一步对它们进行研究,看看在一个窗口里是如何绘制它们的。
在进入之前有一点需要注意,Xlib不能处理许多现在流行的图片格式,例如gif,jpeg或tiff。这些被留给了应用程序(或者是一些图形处理库)来转换成X服务器可以接受的x bitmaps和x pixmaps。
什么是一个X Bitmap?和X Pixmap?
一个Xbitmap是一个有X窗口系统定义的双色图形格式。在保存在一个文件里的时候,bitmap数据看起来就像一段C源程序。它包括定义图片宽高的变量,一个包含比特值得矩阵(矩阵的尺寸=宽*高),和一个可选的热点位置(将会在后面的鼠标光标的部分进行解释)。
一个X pixmap是一个X窗口系统在内存里保存图像的格式。该格式可以储存黑色和白色的图片(例如X bitmaps)也可以保存带颜色的图片。这实际上是X协议唯一能支持的图片格式,任何图片如果想被显示在屏幕上前都要先被转换成这种格式。
实际上,一个X pixmap应该被认为是一个没有被绘制到屏幕上的窗口。许多在窗口上的图形操作也可以工作于X pixmap,只不过使用X pixmap ID来代替窗口ID。事实上,如果你查看手册,你会发现所有的这些函数都是接受一个叫"可画"参数而不是一个窗口参数。因为这两种类型都是可画的,它们都可以被用在例如函数XDrawArc(),XDrawText()等等。
从一个文件里读取一个Bitmap
在图标的程序里,我们已经看过如何从一个文件里把一个Bitmap装载到内存里。前面我们使用的方法是使用C预编译器"#include"来进行,下面我们看看如何直接从文件里读取。
/* this variable will contain the ID of the newly created pixmap. */
Pixmap bitmap;
/* these variables will contain the dimensions of the loaded bitmap. */
unsigned int bitmap_width, bitmap_height;
/* these variables will contain the location of the hot-spot of the */
/* loaded bitmap. */
int hotspot_x, hotspot_y;
/* this variable will contain the ID of the root window of the screen */
/* for which we want the pixmap to be created. */
Window root_win = DefaultRootWindow(display);
/* load the bitmap found in the file "icon.bmp", create a pixmap */
/* containing its data in the server, and put its ID in the ''bitmap'' */
/* variable. */
int rc = XReadBitmapFile(display, root_win, "icon.bmp", &bitmap_width,
&bitmap_height, &bitmap, &hotspot_x, &hotspot_y);
/* check for failure or success. */
switch (rc) {
case BitmapOpenFailed:
fprintf(stderr, "XReadBitmapFile - could not open file ''icon.bmp''./n");
break;
case BitmapFileInvalid:
fprintf(stderr,
"XReadBitmapFile - file ''%s'' doesn''t contain a valid bitmap./n", "icon.bmp");
break;
case BitmapNoMemory:
fprintf(stderr, "XReadBitmapFile - not enough memory./n");
break;
case BitmapSuccess:
/* bitmap loaded successfully - do something with it... */
break;
}
注意对于给定的bitmap参数"root_win"什么作用也不起 - 读取的bitmap并不与这个窗口相联系。这个窗口句柄只是被用来指明bitmap所使用的屏幕。这是非常重要的,bitmap必须支持与屏幕相同数量的颜色,这样它才能发挥作用。
在一个窗口里绘制图形
一旦我们获得了从bitmap里生成的pixmap的句柄,我们就可以使用函数XCopyPlane()把它绘制到窗口里。这个函数可以帮助我们指定什么(一个窗口,甚至另一个pixmap)可以画到这个pixmap上去。
/* draw the previously loaded bitmap on the given window, at location */
/* ''x=100, y=50'' in that window. we want to copy the whole bitmap, so */
/* we specify location ''x=0, y=0'' of the bitmap to start the copy from, */
/* and the full size of the bitmap, to specify how much of it to copy. */
XCopyPlane(display, bitmap, win, gc, 0, 0, bitmap_width, bitmap_height, 100, 50, 1);
如你所见,我们可以只拷贝制定的矩形区而不是整个pixmap。另外还需要注意的是函数XCopyPlane的最后一个参数(那个结尾的"1")。该参数指定了那个平面被从源里拷贝出来。对于bitmaps,我们通常只拷贝平面1。到了下面我们讨论颜色深度的时候你就能确切的明白为什么这么做。
创建一个Pixmap
有时我们需要创建一个没有初始化的pixmap,这样我们可以接下来在它上面绘制。这对图像绘制程序是非常有用的。另外,这对读取各种格式的图像数据也是非常有用的。
/* this variable will store the handle of the newly created pixmap. */
Pixmap pixmap;
/* this variable will contain the ID of the root window of the screen */
/* for which we want the pixmap to be created. */
Window root_win = DefaultRootWindow(display);
/* this variable will contain the color depth of the pixmap to create. */
/* this ''depth'' specifies the number of bits used to represent a color */
/* index in the color map. the number of colors is 2 to the power of */
/* this depth. */
int depth = DefaultDepth(display, DefaultScreen(display));
/* create a new pixmap, with a width of 30 pixels, and height of 40 pixels. */
pixmap = XCreatePixmap(display, root_win, 30, 40, depth);
/* just for fun, draw a pixel in the middle of this pixmap. */
XDrawPoint(display, pixmap, gc, 15, 20);
在一个窗口里绘制一个Pixmap
我们在获得了一个pixmap的句柄后,我们就可以使用它在窗口里绘制,使用函数XCopyArea()。
/* draw the previously loaded bitmap on the given window, at location */
/* ''x=100, y=50'' in that window. we want to copy the whole bitmap, so */
/* we specify location ''x=0, y=0'' of the bitmap to start the copy from, */
/* and the full size of the bitmap, to specify how much of it to copy. */
XCopyPlane(display, bitmap, win, gc, 0, 0, bitmap_width, bitmap_height, 100, 50, 1);
如你所见,我们可以拷贝指定的矩形区域而不是整个pixmap。
另外一个需要被强调注意的是 - 可以在同一个屏幕上创建不同深度的pixmap。当我们进行拷贝作业时(往一个窗口上拷贝pixmap等等),我们应该保证源和目标是用相同的深度。如果两个的深度不一样,操作将会失败。除非我们使用前面介绍的函数XCopyPlane()可以完成这个操作。在那样一种情况下,我们拷贝指定的平面到窗口上去,实际上指定了每一个被拷贝的颜色位。这个操作可以制作许多特殊的效果,但这超出了本文的范围。
释放一个Pixmap
最后,当我们完成了对一个pixmap的操作,我们应该释放它所占的资源。使用函数XFreePixmap()。
/* free the pixmap with the given ID. */
XFreePixmap(display, pixmap);
在释放一个pixmap之后 - 我们绝对不能再访问它。
作为总结这一章,看一下程序draw-pixmap.c 。
改变鼠标光标
我们经常看到改变鼠标光标的程序(经常被称为X光标)。例如,一个正在埋头工作的程序经常会把光标变成沙漏,提示用户需要等待才能处理新的请求。如果没有这么个提示方法,用户会认为程序已经卡住了。下面让我们看看如何为我们的窗口改变鼠标光标。
创建和销毁鼠标光标
系统提供了两个方法来创建光标。其中一个是使用系统预定义的形状,由Xlib支持。另一个是有程序提供一个bitmap来显示。
使用前一种方法时,我们使用一个特殊的字体名字"cursor"和相应的函数XCreateFontCursor()。该函数接受一个形状指示然后返回一个代表生成的光标的句柄。文件
列出了系统支持的光标类型,下面的是其中的三个:
XC_arrow
X服务器显示的普通光标。
XC_pencil
一个笔状的光标。
XC_watch
一个表状沙漏
使用这些符号来创建光标是非常简单的:
#include <X11/cursorfont.h>
/* defines XC_watch, etc. */
/* this variable will hold the handle of the newly created cursor. */
Cursor watch_cursor;
/* create a sand watch cursor. */
watch_cursor = XCreateFontCursor(display, XC_watch);
另一种创建鼠标光标的方法时使用一对pixmaps。一个pixmap定义了光标的形状,另一个是个面具,来指定前一个的什么内容被显示。其它的内容将变成透明的。创建一个那样的光标使用函数XCreatePixmapCursor()。下面的例子里,我们将使用"icon.bmp"来创建光标。我们将假设它已经被装载到内存里去了,并已经被转换成pixmap,返回的句柄被保存到"bitmap"变量里。我们希望它是完全透明的。也就是说,只有黑色颜色的部分会被确实画在屏幕上。为了实现这个效果,我们将会既用它来做光标pixmap且做面具pixmap。希望你能明白为什么这样...
/* this variable will hold the handle of the newly created cursor. *
/Cursor icon_cursor;
/* first, we need to define foreground and background colors for the cursor. */
XColor cursor_fg, cursor_bg;
/* access the default color map of our screen. */
Colormap screen_colormap = DefaultColormap(display, DefaultScreen(display));
/* allocate black and while colors. */
Status rc = XAllocNamedColor(display, screen_colormap, "black", &cursor_fg, &cursor_fg);
if (rc == 0) {
fprintf(stderr, "XAllocNamedColor - cannot allocate ''black'' ??!!??/n");
exit(1);}
Status rc = XAllocNamedColor(display, screen_colormap, "white", &cursor_bg, &cursor_bg);
if (rc == 0) {
fprintf(stderr, "XAllocNamedColor - cannot allocate ''white'' ??!!??/n");
exit(1);}
/* finally, generate the cursor. make the ''hot spot'' be close to the */
/* top-left corner of the cursor - location (x=5, y=4). */
icon_cursor = XCreatePixmapCursor(display, bitmap, bitmap, &cursor_fg, &cursor_bg, 5, 4);
上面需要说明的是参数"hot spot"。当我们定义了一个光标,我们需要指明光标里的哪一个像素用来生成各种鼠标事件。通常,我们根据习惯来指定一个看起来像"hot spot"的点。例如一个箭头形状的光标,我们就会选择箭头尖为"hot spot"。
最后,我们不需要再使用光标时,我们可以使用函数XFreeCursor()来释放它的资源:
XFreeCursor(display, icon_cursor);
设置一个窗口的鼠标光标
我们在创建了光标后,就可以告诉X服务器把它贴到我们的任何窗口上去。使用函数XDefineCursor(),X服务器就会在每一次光标移进指定的窗口时改变光标的形状。稍后我们可以使用函数XUndefinCursor()来撤销刚才的指定。这样,鼠标再移进指定的窗口时就会使用缺省的光标。
/* attach the icon cursor to our window. */
XDefineCursor(display, win, icon_cursor);
/* detach the icon cursor from our window. */
XUndefineCursor(display, win);