使用 XIE 开发图像应用程序
在本文中,我将介绍 X 图像扩展 (XIE),并说明 C 程序员如何使用它来为简单的应用程序添加图像显示支持。关于读者,我们做出以下假设
您能够轻松阅读 C 语言代码。
您熟悉(或至少基本了解)Xlib 开发,例如,创建窗口、绘制文本和图形。由于示例程序使用了 Motif,因此接触过 Xt 和窗口小部件集(如 Motif 或 Xaw)也会有所帮助,但不是必需的。
自 1994 年末以来,我一直在一起使用 XIE 和 Linux。当时,我必须构建自己的 X 服务器(X11R6 的早期测试版)以支持 XIE,而且我还必须将客户端库 (libXIE.a) 移植到 Linux,因为在任何 Linux 发行版中都还没有支持 XIE 的 X 环境。现在,所有当前可用的 Linux 发行版都为 XIE 客户端开发提供了非常充分的平台,以及使用 XIE 的客户端所需的运行时支持。
在 X 中,客户端(即应用程序)连接到 X 服务器,您可以将其视为基本上只不过是一个连接了键盘和鼠标的显示器。核心 X 协议提供了客户端在服务器上生成用户界面所需的所有功能。使用核心 X 协议,客户端可以
创建、移动和销毁窗口。
将图形和文本渲染到窗口和屏幕外像素图。
接收服务器上发生的、客户端感兴趣的事件的通知。此类事件包括按钮按下、鼠标移动、键盘按下、窗口曝光通知等等。
对于你们大多数人来说,客户端和服务器都在同一台机器上运行,即在单个 Linux 系统上,但不必如此。X 窗口系统协议的设计使得客户端和服务器之间的通信可以通过网络连接(例如 TCP/IP)进行。(如果本地运行,则为本地连接,即 AF_UNIX 连接。)因此,您可以拥有如图 1 所示的情况。在那里,X 服务器是我家里的 Linux 机器。时钟应用程序和终端模拟器 (xterm) 是在我的 Linux 机器本地运行的进程。此外,我还有到我的 ISP 的 PPP 连接(它正在运行 Solaris 或其他一些 Linux 模仿者),并且在 xterm 中,我正在我的本地机器和 ISP 之间执行 telnet 会话。另两个窗口 (xiegen) 显示在我的 ISP 机器上远程执行的应用程序。此应用程序正在我的 Linux 机器上显示其客户端窗口,响应我的 Linux 机器上发生的键盘和鼠标事件,这要归功于 X 协议。在执行远程应用程序之前,我需要将 DISPLAY 环境变量设置为我的 Linux 机器的 IP 地址,该机器充当服务器。Xlib(在远程主机上)在客户端启动时读取此变量,并使用其值打开与在我的 Linux 机器上运行的 X 服务器的连接。以下行中的 :0 字符表示服务器上的逻辑显示
$ typeset -x DISPLAY=123.45.67.89:0
请注意,除了屏幕、鼠标和键盘之外,图 1 中生成 xiegen 输出的客户端正在与远程 Solaris 主机上的资源交互。如果我的远程客户端打开一个文件,例如 /etc/passwd,它是在 Solaris 主机上打开 /etc/passwd,而不是在 Linux 主机上。
实际上,通过 RS-232 连接从哑终端运行基于控制台的 UNIX 或 Linux 应用程序与通过网络连接从 X 服务器运行 UNIX 或 Linux 应用程序有很多共同之处,只是当使用 X 时,图形支持要好得多。
供应商可以通过扩展协议将其他功能添加到核心 X 协议。XIE 就是一个例子。其他扩展协议包括 X 的 Phigs 图形扩展 (PEX) 和形状扩展,它允许 X 显示非矩形窗口。还有许多其他扩展;执行 xdpyinfo 命令以查看您的 X 服务器支持哪些扩展。
XIE 是在 1994 年 7 月发布的第一个版本的 X11R6 中发布的扩展。开发 XIE 的目的是为客户端提供以下领域的支持
客户端和服务器之间(在任一方向上)的图像数据传输
图像增强和处理
图像显示
核心 X 协议仅为图像数据的传输、处理和显示提供最少的支持。让我们更详细地了解这些领域中的每一个,并讨论核心 X 在成像支持方面缺少什么,以及 XIE 带来了什么。
为了显示图像数据,客户端必须首先将图像从客户端传输到服务器。图像数据的编码以及传输的效率是 XIE 解决的两个主要问题。
核心 X 要求客户端使用 X 特定的编码传输图像数据。例如,如果客户端正在处理 JPEG 图像数据,则它必须解码 JPEG 图像数据并将其转换为 X 特定的编码,然后才能将其发送到服务器。另一方面,XIE 能够接收和解码以几种流行的图像编码(包括 JPEG Baseline 和 CCITT FAX 编码 G31D、G32D 和 G42D)编码的图像数据。但是,XIE 支持的编码列表对于某些人来说可能看起来相当有限;不支持 GIF,因为 LZW 存在与其相关的许可问题,并且 PNG 在最新版本的 XIE 之前尚未发明。供应商可以自由添加到支持的编码列表中。但是,真正可移植的应用程序将仅支持协议规范定义的编码。XIE 支持两种编码,UncompressedSingle 和 UncompressedTriple,它们可用于传输未压缩的双色调、灰度和彩色图像。客户端可以使用这些编码发送“原始”图像数据,或者他们可以将来自不受支持的编码(例如 LZW)的图像数据转换为 UncompressedSingle 或 UncompressedTriple 之一,然后再进行传输。
在效率方面,一个 8 位 640x480 灰度图像大约为 2.3MB 大小,而彩色版本(24 位,640x480)是其三倍大。传输如此大量的数据成本很高。由于 XIE 允许以编码形式(例如 JPEG)传输图像,因此性能得到提高,因为客户端和服务器之间发送的字节更少。
一旦进入服务器,图像数据就可以缓存在称为光图的本地图像存储资源中。这样做可以大大减少交互式成像应用程序中网络带宽的使用。
在核心 X 中,客户端对图像数据的处理以非常低的级别执行:像素值只能从图像中读取或写入图像。更高级别的操作(例如图像缩放、图像算术和混合)必须由客户端利用这些原语来实现。此外,所有处理都必须在客户端执行,处理后的像素值在处理完成后从客户端通过网络传输到服务器。
在 XIE 中,图像处理完全在服务器端执行。如果 XIE 不支持所需的操作,则可以在客户端完成。XIE 支持高级操作,允许客户端
提高图像数据的质量。
执行几何操作,例如缩放、轴翻转和旋转。
准备图像数据以在属于特定 X Visual 类的窗口中显示。
交互式成像应用程序最好使用服务器端处理和使用光图资源进行图像缓存的组合来实现。
要对图像数据执行的操作,包括图像解码、图像处理和增强以及图像显示,由称为光流的数据结构描述。光流是有向无环图(意味着它不能有循环),由光流元素组成。光流图中的每个光流元素执行特定的原子操作,将其结果传递给下游的元素。光流头部的元素称为导入元素,用于读取和解码客户端发送到光流的图像数据。如果需要,它们还直接从光图资源读取图像数据。下游的元素称为处理元素,执行图像处理任务。导出元素用于将图像数据路由到窗口、光图资源或返回客户端,并在光流图的末尾找到。
图 2 说明了一个简单的光流图,我们将在后面的示例中使用它。第一个光流由两个元素组成:ImportClientPhoto (ICP) 和 ExportPhotomap (EP)。ImportClientPhoto 在此处用于读取和解码 JPEG 图像。ExportPhotomap 从 ImportClientPhoto 读取结果并将其存储在称为光图的服务器端资源中。箭头显示图像数据如何在光流图中流动。可能存在更雄心勃勃的光流。总而言之,涉及光流拓扑的规则是
光流中的所有路径都必须以导入元素开始,后面可以跟一个或多个处理元素,并且必须以导出元素结束。
光流图中不允许有循环。
如前所述,ImportClientPhoto 读取客户端发送的图像数据。客户端必须向 ImportClientPhoto 指定要发送的图像数据的编码,并且必须在构建光流时执行此操作。这是通过将技术常量作为参数传递给用于将 ImportClientPhoto 添加到光流图的函数(光流元素便捷函数)来完成的。例如,要解码 TIFF-PackBits 编码的图像数据,客户端将常量 xieValDecodeTIFFPackBits 作为参数传递给 XieFloImportClientPhoto。此外,大多数技术都需要一组技术参数。这些参数更精确地定义了技术将如何执行其任务。技术参数通过传递指向包含所需信息的结构的指针来指定。XIElib 提供了可用于分配和初始化这些结构的便捷函数。大多数导入、处理和导出元素都支持技术和技术参数。在某些情况下,客户端可以指定默认技术。在这种情况下,不提供技术参数,因为服务器决定适当的默认值。
所有技术通用的参数都使用光流元素便捷函数的参数提供。例如,XieFloImportClientPhoto 采用宽度、高度和级别参数。级别参数是一个包含三个长整数的数组,它按波段指定图像的深度以及它可以表示的颜色数量。例如,如果我们处理的是 24 位彩色图像,则级别将设置为 {256,256,256}。
这些元素从客户端或服务器资源读取数据。导入元素是图像数据如何可用于光流进行处理的方式。XIE 支持的导入元素显示在侧边栏 “导入元素”中。
这些元素从光流图中较早的元素读取图像数据,并在将其传递给下游元素之前处理图像数据。
大多数处理元素都能够透明地处理 SingleBand(灰度或双色调)或 TripleBand(彩色)图像数据。一些处理元素只允许一个输入,一些操作一个或两个输入,而一个(BandCombine)需要三个 SingleBand 输入。处理元素的输出是图像数据。例如,Arithmetic 接受两个图像,或一个图像和一个常量,并将它们逐像素相加;结果是其输出。XIE 支持的处理元素显示在侧边栏 “处理元素”中。XIE 支持的导出元素显示在侧边栏 “导出元素”中。
XIE 缺少的一件事是客户端对图像文件格式处理的支持。图像文件格式定义了图像数据在磁盘上的存储方式。例如,JPEG 编码的图像数据可以存储在 TIFF 或 JFIF 格式的文件中。为什么需要文件格式?它们组织了图像数据、调色板和描述图像宽度、高度和深度的标头信息在文件中的存储方式。XIE 的问题在于,客户端需要找出特定文件包含的内容,并提供读取图像数据和标头信息所需的代码。同样,需要标头信息是因为客户端必须告诉 XIE 它将发送给 ImportClientPhoto 的图像数据。
互联网包含处理此问题的良好资源。请参阅我的主页(URL 在本文末尾给出)的 XIElib 示例代码部分,以下载处理此问题的示例代码。示例代码基于两个库,与我的示例一起提供,并且在网络上的其他地方也可用。第一个库 libtiff 可用于从 TIFF 文件读取标头信息和编码的图像数据。另一个库可用于从 JFIF (JPEG) 格式文件提取标头信息和编码的图像数据。
让我们转到为虚构应用程序添加图像支持的具体细节。图 3 说明了一个简单的 Motif 客户端,我将在本文的其余部分开发它。示例客户端读取当前目录中以 .emp 结尾的文件。有关特定员工的信息存储在这些文件中。例如
123 12 Barney Smith 1124 Boogie Woogie Avenue Bedrock CA 91911 Individual Contributor 32000 barney.img
第一行是员工编号 (123)。第二行是部门编号 (12)。紧挨着最后一行的行是他的工资 (32000)。最后一行是包含员工 256x256 JPEG 图像的文件。为了简单起见,我使用了固定宽度和高度的图像,以避免执行缩放的需要。这并不是因为 XIE 不支持缩放。XID 的 Geometry 元素(用于执行缩放)将需要单独的文章才能完全描述。
在图 3 中,员工编号显示在 GUI 左侧的滚动列表中。当用户单击员工编号时,GUI 会显示员工的图片以及从文件中读取的其他数据。
在以下代码中,我将忽略与记录读取和 Motif GUI 相关的问题。如果您对这些领域有具体问题,我很乐意通过电子邮件回答。将我们的任务视为如下:我们有一个 Motif 应用程序,它完成了上述所有操作,只是它缺乏显示员工图片的能力。我们添加了显示图像区域所需的 Motif 代码(使用 DrawingArea 窗口小部件)。我们的任务是将图像显示功能添加到应用程序中,并且我们需要提出一个使用 XIE 的解决方案。
所有 XIE 客户端应用程序都必须使用以下预处理器指令包含文件 XIElib.h
#include <X11/extensions/XIElib.h>
以下包含文件是我自己创建的,并在我的网站上与示例代码一起提供。需要它来定义我将在本文后面描述的光流后端代码使用的数据结构。
#include "backend.h"在 main 之前,还声明了几个静态全局变量
static XieExtensionInfo *xieInfo; static XiePhotospace photospace;XIE 应用程序必须做的第一件事是建立与 XIE 扩展的连接。
这在连接到显示器后完成。在我们的主应用程序中,就在我们使用 XOpenDisplay 或某些等效项建立与服务器的连接之后,我们使用以下代码连接到 XIE
if ( !XieInitialize( display, &xieInfo ) ) { fprintf( stderr, "XIE not supported on this display!\n"); exit( 1 ); }
变量 xieInfo 是表示与 XIE 连接的句柄。其字段包含有关 display 指向的服务器上 XIE 功能的信息。大多数客户端无需关心结构的内容,除非在处理 XIE 错误和事件时(我在此处不讨论)。
接下来,我们声明一个光空间。这表示一个上下文,其中即时光流可以在服务器上执行。即时光流是使用名为 XieExecuteImmediate 的单个 XIElib API 调用创建和执行的。在即时光流执行后,它会被 X 服务器销毁。另一方面,存储的光流会持久存在于服务器上,直到显式销毁,或者创建客户端断开服务器连接并且没有其他客户端引用该光流。存储的光流可以多次执行。我们的示例客户端需要一个光空间,因为我们在示例中执行即时光流。这是通过调用 XieCreatePhotospace 来完成的
photospace = XieCreatePhotospace( display );
在我们调用 XtAppMainLoop 来处理应用程序的 GUI 之前,会调用我们提供的名为 LoadEmp 的例程。LoadEmp 读取当前目录中找到的所有 .emp 文件,并将它们存储在 EmpDat 结构的链表中。LoadEmp 还调用例程 LoadImage,传递指向包含图像数据文件名称的 EmpDat 结构的指针。LoadImage 读取文件并使用 XIE 将图像数据存储在服务器上。LoadImage 读取的图像数据存储在 JFIF 文件中,并编码为 JPEG Baseline,XIE 支持的编码。LoadImage 同时支持彩色和灰度 JPEG 图像。
让我们仔细看看 LoadImage。LoadImage 执行以下操作
它在 X 服务器上创建一个光图资源。光图资源将用于保存或缓存服务器上的图像数据,以便我们只将其通过网络传输到 X 服务器一次。图像数据将以未压缩格式存储在光图资源中。
它从指定的文件中读取 JPEG 图像,获取图像数据以及标头信息,即宽度、高度、级别和波段数(灰度或彩色)。
它构建一个光流以从客户端接收图像数据,对其进行解码并将其存储在光图资源中。用于处理彩色图像数据和灰度图像数据的光流不同。两个光流都使用 ImportClientPhoto 来接收和解码图像数据,并使用 ExportPhotomap 将结果存储在光图资源中。如果图像是 TripleBand(彩色),我们在 ImportClientPhoto 和 ExportPhotomap 之间添加第三个元素 ConvertToRGB,以将图像数据从 YCbCr 色彩空间转换为 RGB 色彩空间。窗口中显示的所有图像数据都必须对应于 RGB 色彩空间,因为视频显示器是 RGB 设备。
它执行光流并发送图像数据。
它将光图资源的资源 ID 存储在 LoadImage 通过引用传递的 EmpDat 结构中,以便我们稍后可以使用它。我们还记录 JPEG 图像中的级别数(3 或 1);这是确定如何在显示之前从图像数据生成像素数据所必需的。
以下是 LoadImage 代码
int LoadImage( EmpDat *newp ) { int floSize, size, decodeTech, floId = 1, idx; Bool notify; short w, h; char d, l, *bytes; XieConstant bias; XiePointer decodeParms; XiePhotoElement *flograph; XieYCbCrToRGBParam *rgbParm = 0; XieLTriplet width, height, levels;现在我们创建一个光图资源并将结果存储在 newp 中以供以后使用。
if ( ( newp->pmap = XieCreatePhotomap( display ) ) == (XiePhotomap) NULL ) return( 1 );GetJFIFData 是我的网站上提供的一个例程,它读取 JFIF 文件以获取图像数据和标头信息。我们接下来使用它
if ( ( size = GetJFIFData( newp->image, &bytes, &d, &w, &h, &l )) == 0 ) { XieDestroyPhotomap( display, newp->pmap ); fprintf( stderr, "Problem getting JPEG data from %s\n", newp->image ); return( 1 ); } newp->bands = l;此示例仅支持 8 位灰度或 24 位彩色 (8,8,8) 图像数据。
if ( d != 8 ) { XieDestroyPhotomap( display, newp->pmap ); fprintf( stderr, "Image %s must be 256 levels\n", newp->image ); return( 1 ); }XieAllocatePhotofloGraph 分配一个光流图,然后我们用元素填充它。如果我们处理的是灰度图像数据 (l == 1),我们只需要两个元素。如果我们处理的是彩色图像数据 (l == 3),我们需要第三个元素将图像从 YCbCr 色彩空间转换为 RGB。
floSize = (l == 3 ? 3 : 2 ); flograph = XieAllocatePhotofloGraph(floSize );设置 XieFloImportClientPhoto 的宽度、高度和级别参数。此信息是通过从 JFIF 文件读取标头信息获得的。
width[0] = width[1] = width[2] = w; height[0] = height[1] = height[2] = h; levels[0] = levels[1] = levels[2] = 256;图像(SingleBand 或 TripleBand)是 JPEG Baseline,因此指定相应的解码技术并分配所需的技术参数。解码技术和技术参数也传递给 XieFloImportClientPhoto。
decodeTech = xieValDecodeJPEGBaseline; decodeParms = ( char * ) XieTecDecodeJPEGBaseline( xieValBandByPixel, xieValLSFirst, True);现在我们可以将 ImportClientPhoto 添加为光流图的第一个元素。
idx = 0; notify = False; XieFloImportClientPhoto( &flograph[idx], /* address of element * in photoflo graph */ (l == 3 ? xieValTripleBand : xieValSingleBand), /* data class */ width, /* width of each band */ height, /* height of each band */ levels, /* levels of each band */ notify, /* send DecodeNotify event? */ decodeTech, /* decode technique */ decodeParms /* decode parameters */ ); idx++;如果图像是彩色的,则从 YCbCr 转换为 RGB。XieTecYCbCrToRGB 用于分配 YCbCrToRGB 技术所需的技术参数。分配的技术参数和技术都传递给 XieFloConvertToRGB,它用于将 ConvertToRGB 元素添加到光流图中。讨论使用的参数和技术参数超出了本文的范围,但以下代码应该适用于应用程序遇到的大多数彩色 JPEG Baseline 图像。
if ( l == 3 ) { bias[ 0 ] = 0.0; bias[ 1 ] = bias[ 2 ] = 127.0; levels[ 0 ] = levels[ 1 ] = levels[ 2 ] = 256; rgbParm = XieTecYCbCrToRGB( levels, (double) 0.2125, (double) 0.7154, (double) 0.0721, bias, xieValGamutNone, NULL ); XieFloConvertToRGB( &flograph[idx], idx, xieValYCbCrToRGB, (XiePointer) rgbParm ); idx++; }光流中的最后一个元素是 ExportPhotomap。使用的编码技术是 xieValEncodeServerChoice。给定我们正在处理的光流,这应该导致 XIE 将图像以未压缩的规范格式存储在光图资源中。
XieFloExportPhotomap( &flograph[idx], idx, newp->pmap, xieValEncodeServerChoice, (XiePointer) NULL); idx++;现在我们有了一个光流图,我们可以将其发送到服务器并通过调用 XieExecuteImmediate 来启动其执行
XieExecuteImmediate( display, photospace, floId, False, flograph, floSize );执行开始后,光流将被阻塞,等待来自客户端的图像数据。发送此数据的 XIElib 函数是 XiePutClientData,它可用于将任何客户端数据(ROI、LUT 和图像)发送到等待数据的 ImportClient 元素。PumpTheClientData 是我编写的实用程序函数(也可在我的网站上找到),它是 XiePutClientData 的包装器,使将数据发送到 ImportClient 元素的过程变得更容易一些。
PumpTheClientData( display, floId, photospace, 1, bytes, size, sizeof(char), 0, True );此时,图像数据已被光流读取、解码、转换为 RGB 色彩空间(如果是彩色)并存储在服务器端光图缓存中以供以后使用。此外,我们执行的光流已被服务器销毁。现在,我们需要释放分配给光流图和上述代码中其他项目的内存。
if ( rgbParm ) XFree( rgbParm ); free( bytes ); XieFreePhotofloGraph( flograph, floSize ); XFree( decodeParms ); return( 0 ); }我们需要将图像数据从光图资源传输到窗口的代码。两种不同的情况将导致客户端执行实际绘制
用户从滚动列表中选择员工编号。
DrawingArea 窗口小部件的窗口变得暴露,并且未启用服务器后备存储。
XtSetArg(args[0], XmNselectionPolicy, XmSINGLE_SELECT); list_w = XmCreateScrolledList(rowcol, "scrolled_list", args, 1); XtAddCallback(list_w, XmNsingleSelectionCallback, ListCallback, NULL); XtManageChild(list_w);因此,当用户单击项目时,Xt 将调用函数 ListCallback。在 ListCallback 内部,我们执行以下任务
确定选择了哪个员工,并获取指向相应 EmpDat 结构的指针。
使用有关员工的信息(例如,姓名、部门、地址和工资)更新对话框中的文本字段。
调用一个函数以在 DrawingArea 窗口小部件拥有的窗口中显示员工图像。
static void ListCallback(Widget list_w, XtPointer client_data, XmListCallbackStruct *cbs) { char *choice, buf[ 32 ]; EmpDat *p; /* Read the list item, and then look it up in our * linked list of employee records */ XmStringGetLtoR(cbs->item, charset, &choice); p = FindChoice( choice ); XtFree(choice); /* If we have a match, display the text * information in the dialog */ if ( p != (EmpDat *) NULL ) { /* first do the text fields */ sprintf( buf, "%d", p->code ); XmTextFieldSetString( codeT, buf ); XmTextFieldSetString( nameT, p->name ); XmTextFieldSetString( streetT, p->street ); XmTextFieldSetString( cityT, p->city ); XmTextFieldSetString( stateT, p->state ); XmTextFieldSetString( zipT, p->zip ); XmTextFieldSetString( descT, p->desc ); sprintf( buf, "%ld", p->salary ); XmTextFieldSetString( salaryT, buf ); /* Go and display the image. gDrawP is discussed * later */ gDrawP = p; DisplayPhotomap( p ); } }与将图像数据从光图传输到窗口相关的实际工作的例程是 DisplayPhotomap。它是一个单独的例程(即,不是 ListCallback 的一部分),因为我们需要在处理窗口曝光时调用它。
void DisplayPhotomap( EmpDat *p ) { XiePhotoElement *flograph; Visual *visual; Backend *backend; int floId = 1, screen, idx, floSize, beSize; Display *display; if ( p == (EmpDat *) NULL ) return;我们做的第一件事是为我们正在构建的光流生成后端。后端是一组处理元素,加上 ExportDrawable,或者如果图像是双色调的,则为 ExportDrawablePlane。这些元素的目的是准备图像数据以在指定的窗口中显示。后端负责以下事项
确保图像的级别属性与目标窗口相对应。例如,如果图像是 8 位彩色,并且我们正在显示到 1 位 StaticGray 窗口,我们必须在后端插入一个 Dither 元素,以将图像的级别从 256 降低到 2。在这些情况下,Dither 是保留图像内容的最佳方法。其他处理元素可以完成这项工作,但会严重破坏结果。如果图像的级别属性和窗口的功能不匹配,XIE 将生成错误并中止光流。
将图像强度值转换为颜色映射索引数据。从 Xlib 编程中,回忆起诸如以下代码,该代码从特定的颜色映射分配红色
Display *display; /* server connection */ int screen; /* usually 0 */ Colormap cmap; /* resource ID of color map */ XColor color; /* holds info about a color */ cmap = DefaultColormap( display, screen ); color.red = 65535; color.green = color.blue = 0; XAllocColor( display, cmap, &color );
现在,我们可以使用返回的颜色来绘制,例如,通过将与窗口关联的 GC 的前景色设置为 XAllocColor 返回的像素值,在窗口中绘制一条红线XSetForeground( display, GC, color.pixel ); XDrawLine( display, window, gc, x1, y1, x2, y2 );
因此,当我们想要在窗口中绘制特定颜色的线时,我们实际上将像素值绘制到窗口中,该像素值索引与窗口关联的颜色映射中的颜色。显示图像时也必须发生同样的事情。X 期望我们的窗口包含像素值。服务器(硬件)获取这些像素值并将其转换为我们在屏幕刷新时看到的颜色。将图像中的颜色映射到一组像素值的便捷方法是将 ConvertToIndex 元素添加到光流后端。ConvertToIndex 的工作是将所有颜色值转换为像素,并在颜色映射中分配任何需要的单元格。后端的最后一项任务是在窗口中显示图像。如果图像是灰度或彩色的,我们添加一个 ExportDrawable 元素作为后端的最后一个元素。如果图像是双色调的,那么我们使用 ExportDrawablePlane。
display = XtDisplay( drawingArea ); screen = DefaultScreen( display ); visual = DefaultVisual( display, screen ); if ( p->bands == 1 ) backend = (Backend *) InitBackend( display, screen, visual->class, xieValSingleBand, 1<<DefaultDepth( display, screen ), -1, &beSize ); else backend = (Backend *) InitBackend( display, screen, visual->class, xieValTripleBand, 0, -1, &beSize); if ( backend == (Backend *) NULL ) { fprintf( stderr, "Unable to create backend\n" ); exit( 1 ); }现在我们已经处理了后端,我们分配光流图并将 ImportPhotomap 添加为它的第一个元素。我们将从中读取图像的光图的资源 ID 传递给 XieFloImportPhotomap。此资源 ID 存储在作为参数传递给此例程的 EmpDat 结构中。
floSize = 1 + beSize; flograph = XieAllocatePhotofloGraph( floSize ); idx = 0; XieFloImportPhotomap( &flograph[idx], p->pmap, False ); idx++;接下来,调用 InsertBackend,它将后端元素添加到光流图中。
if ( !InsertBackend( backend, display, XtWindow( drawingArea ), 0, 0, gc, flograph, idx ) ) { fprintf( stderr, "Unable to add backend\n" ); exit( 1 ); }现在我们有了一个光流图,我们调用 XieExecuteImmediate,它负责将光流传输到服务器并执行它。由于光流是即时的,因此一旦执行完成,它将被服务器销毁。此时,光图中的图像数据应该在 DrawingArea 窗口小部件的窗口中对用户可见。
XieExecuteImmediate( display, photospace, floId, False, flograph, floSize ); XieFreePhotofloGraph( flograph, floSize ); CloseBackend( backend, display ); }要讨论的最后一个例程是 RedrawPicture。这个简单的例程是一个回调,在 DrawingArea 窗口小部件实例中注册,以便在 DrawingArea 窗口小部件的窗口接收到 expose 事件时调用。回想一下,ListCallback 将指向对应于用户列表选择的 EmpDat 结构的指针存储到一个名为 gDrawP 的全局变量中。因此,gDrawP 保存指向当前显示员工数据的指针。我们在 RedrawPicture 中需要做的就是检查 gDrawP 是否指向有效数据;如果是,我们知道用户之前已经做出了选择。现在,我们可以调用 DisplayPhotomap,将 gDrawP 作为参数传递,以将图像渲染到窗口。
static void RedrawPicture(Widget w, XtPointer client_data, XmDrawingAreaCallbackStruct *cbs) { if ( gDrawP != (EmpDat *) NULL ) DisplayPhotomap( gDrawP ); }
本文讨论的示例的完整源代码以及构建它所需的库例程可以在我的主页上找到,该主页位于 Internet 上的 http://www.users.cts.com/crash/s/slogan/。本文仅介绍了您将在那里找到的 40 多个示例客户端之一。我在 XIElib 编程方面的著作《使用 XIElib 开发图像应用程序》由 Prentice Hall 出版,其中包含比我在这篇长篇文章中可以提供的详细信息。有关我的书的更多信息,可以在我的网站上或 http://www.prenhall.com/ 上找到。如果您对 XIE、本文、我的其他示例或 X11 有任何疑问,请随时给我发送电子邮件。
