使用 C 语言进行图像处理的 Tcl/Tk

作者:Siome K. Goldenstein

从头开始用 C 语言实现图像处理(或操作)程序是一项艰巨的任务。不仅需要开发内部数据结构,还需要编写用于读取和写入可用图形格式的过滤器。界面设计和实现也很困难,因为需要处理诸如颜色分配、量化等问题。在本文中,我们将向您展示 Tcl 和 Tk 如何帮助您轻松处理这些问题。但是,应该注意的是,某些图像操作在计算上是密集型的,这使得使用 Tcl 的成本过高。因此,我们将混合使用 Tcl 和 Tk 与 C 语言,并获得两者的优点。

Linux Journal 第 10 期(1995 年 2 月)中,Matt Welsh 撰写了一篇不错的文章,描述了如何使用 Tcl/Tk 作为 C 程序的 前端,使用管道与 wish 进程进行通信。虽然这种方法有很多优点,例如,在使用静态库时实现简单且节省内存,但它确实存在一些局限性

  • 首先,由于您的程序“拆分”为两个不同的进程,因此资源共享并非易事。

  • 其次,所有通信都通过管道完成,给系统增加了额外的负担。

在本文中,我们使用 Tcl/Tk 作为核心程序的扩展来解决这个问题,并展示了以这种方式解决问题的一些优势。

一个实际的例子:让我们抖动

我们将从编写一个小程序开始,对图像的选定子矩形进行特殊的抖动(半色调),以创建特殊效果。

所描述的技术将彩色像素的垂直条纹转换为黑白像素的垂直条纹,其中平均强度最接近原始平均值。此外,所有黑色像素都集中在中心。(这种效果已经在娱乐行业中使用了一段时间。)见图 1。以下部分描述了完成此效果的必要步骤。

Tcl/Tk with C for Image Processing

图 1. 对一个矩形区域进行抖动后程序的典型外观。

Tk 中的图像

我们程序的基础是 image 原语,它首次出现在 Tk 4.0 版本中。其思想是创建一个带有关联命令的“图像对象”,就像任何普通的小部件一样。

图像可以是两种不同的类型:位图和照片。虽然位图仅处理前景和背景颜色,但照片处理真彩色对象,自动量化显示中可用的颜色数量。让我们关注“照片”类型,它是由 Paul Mackerras 基于他早期的“照片小部件”实现的。

用于创建名为“picture”的图像对象,并将图像文件设置为“mickey.gif”的命令将是

image create photo picture -file mickey.gif

创建后,我们可以轻松地进行一些操作。例如,获取其尺寸

set pic_wid [image width picture]
set pic_hei [image height picture]
您还可以创建第二个图像,并将第一个图像的一部分复制到第二个图像中
image create photo pic_piece
pic_piece copy picture -from 0 0
        [expr $pic_wid/2] [expr $pic_hei/2]
在复制过程中,您可以使用 Tk 选项 subsamplezoom 来缩小图像或放大图像的一部分。复制的部分可以放置在目标图像内的任何位置。

可以指定给定图像的颜色立方体的大小(您甚至可以显式地将其设置为灰度)、伽玛校正和其他一些巧妙的东西。查看 photo 手册页以获取详细信息。

查看图像并允许进行一些操作的好方法是将图像视为“画布对象”

canvas .c
pack .c
 .c create image 1 1 -anchor nw -image picture
                -tags "myimage"

创建后,您可以绘制和操作任何您想要的画布对象,就像它漂浮在 myimage 上一样。只需记住将图像保持为“较低”的对象,这样您始终能够看到其他所有内容。可以通过给出以下内容来完成此定位

 .canvas lower myimage
Tcl/Tk 作为 C 程序的接口

让我们对两种 C-Tcl/Tk 应用程序做一个小的区分:一种是充当 shell 的应用程序(例如 wish),另一种是以预定方式使用 Tcl/Tk 扩展的应用程序。

如果您想使用您创建的一些额外命令创建 wish 的另一个“实例”,您应该阅读有关 Tcl_MainTcl_AppInit 的手册页。

如果您的程序仅将 Tcl/Tk 用于界面,并且不打算以“类 shell”的方式使用,则方法略有不同。我建议您获取 tkHelloWorld.tar.gz 演示程序(请参阅侧边栏)作为示例。

基本上,您的程序必须实现以下四个步骤

  • 初始化 Tcl 和 Tk。

  • 创建负责调用 C 例程的 Tcl 命令。

  • 要求 Tcl 评估“接口描述”文件。

  • 让 Tk 控制程序的主要流程。

在清单 1 中显示的 C 代码中,注释准确地标识了正在执行的四个步骤中的哪一步。

清单 1

从现在开始,我们希望仅将 C 编程用于某些关键功能,因为我们应用程序的主要流程和控制由 Tk 处理。

从 Tcl 调用 C 函数

如果您对调用 C 例程的无数种方法感兴趣,请阅读 John K. Ousterhout 于 1994 年在 Addison-Wesley 出版的 TCL and the TK Toolkit

本质上,您的 C 函数必须具有如下原型

int
C_func_name (ClientData cd, Tcl_Interp *interp,
             int argc, char **argv);

您必须通过以下方式注册它

Tcl_CreateCommand (interp, "Tcl_func_name",
        C_func_name, (ClientData *) NULL,
        (Tcl_CmdDeleteProc *) NULL);
然后,每当 Tcl 遇到命令 Tcl_func_name 时,它将调用您的例程,该例程将接收 Tcl 参数,就像 main 从 shell 接收 argcargv 参数一样,即 argc 将是单词数,而 argv 将是“字符串向量”。
来回传递图像

我们希望我们的 C 例程在 Tk 下处理名为 image_name 的图像。直接的解决方案是反复传递每个像素的颜色(照片小部件具有此选项),直到图像完成。当此程序运行时,我们可以出去吃午饭、拜访一些朋友、吃晚饭并看电影。但是,有一种更好的方法可以实现该目标。从 C 语言中,我们要求 Tk 来处理它。首先,我们必须定义

Tk_PhotoHandle      image;
Tk_PhotoImageBlock  blimage;

然后按顺序调用以下函数

image = Tk_FindPhoto ("image_name");
Tk_PhotoGetImage (image, &blimage);
图像在 blimage 中,这是一个在 tk.h 中定义的结构,如下所示
typedef struct {
  unsigned char *pixelPtr;
  int width;
  int height;
  int pitch;
  int pixelSize;
  int offset[3];
} Tk_PhotoImageBlock;
所有颜色信息都以无符号字符(值介于 0 和 255 之间)的形式出现。pixelPtr 是第一个像素(左上角)的地址。widthheight 定义图像尺寸,而 pixelSize 是两个水平相邻像素之间的地址差,pitch 是两个垂直相邻像素之间的地址差。最后,offset 数组包含从像素地址到包含红色、绿色和蓝色分量的字节地址的偏移量。

使用上述定义允许图像的不同表示形式;例如

  • 定义一个维度为三个字节的点,每个颜色分量一个字节。那么 pixelSize 为 3,offset 为 0、1 和 2,pitch 为宽度的三倍。

  • 将彩色图像视为三个平面(图像),每个颜色一个平面。那么 pixelSize 为 1,offset 为 0、width*height2*width*height。最后,pitch 等于宽度。

可以使用三个简单的 C 宏来获取给定像素的颜色

#define RED(p,x,y)  ((p)->pixelPtr[(y)*(p)->
pitch + (x)*(p)->pixelSize + (p)->offset[0]] )
#define GREEN(p,x,y) ((p)->pixelPtr[(y)*(p)->
pitch + (x)*(p)->pixelSize + (p)->offset[1]])
#define BLUE(p,x,y)  ((p)->pixelPtr[(y)*(p)->
pitch + (x)*(p)->pixelSize + (p)->offset[2]])

您调用宏时,将上面解释的块结构的地址作为第一个参数,并将像素的 x 和 y 坐标(其中 0,0 是左上角)作为第二个和第三个参数。对于优化的程序,使用地址差来确定下一个像素相对于当前像素的位置(即其邻居)会更快。

关于程序

此程序的完整 C 代码在清单 1 中,Tcl 代码在清单 2 中。

图 2 是程序运行中的快照。

Tcl/Tk with C for Image Processing

图 2. 当垂直尺寸为 5 时,所有可能的输出集群的示例。选择标准是最近的平均值。

清单 2

该程序可以从以下网址下载:ftp://ftp.impa.br/pub/visgraf/people/siome/lj/ljdither.tgz。

关于 C 和 Tcl/Tk 交互的重要说明

当 Tcl/Tk 调用 C 中的函数时,它仍然可以接收界面事件,例如按钮按下或滑块移动;但是,它无法运行绑定到这些事件的关联脚本(或 C 函数),因为目前 C 函数控制流程。

一个很好的例子是质量弹簧模拟器,其中 C 函数有一个循环来执行模拟和画布绘制。如果在模拟过程中能够更改常量,甚至在预定时间之前中止模拟,那将是太好了。长 Tcl 脚本也需要此选项。两种情况下的解决方案都是不时使用 update 命令来处理用户输入。

来自 update 手册页

不带选项的 update 命令在您执行长时间运行的计算但仍希望应用程序响应用户交互的脚本中很有用;如果您偶尔调用 update,则用户输入将在下次调用 update 时处理。

结论

通过让 Tcl/Tk 处理界面,C 处理程序的关键任务,可以实现强大的组合。

在 Internet 上可以找到许多有用的额外小部件,用于使用声音(请参阅 tkSound)、移动对象等等。这些小部件的集成原理是相同的——您可以创建一个新的类似 wish 的 shell,或者将新的可用函数与您自己的一些额外 C 代码一起使用。

另一个好的软件包是 Tix,它包含在许多 Linux 发行版中。它为 Tk 添加了许多精彩的小部件,并采用面向对象的方法来构建新的“巨型小部件”。

希望您觉得这篇文章有用,并祝您编程愉快。

资源

Tcl/Tk with C for Image Processing
Siome Goldenstein 是一名电子工程师,目前正在完成计算机图形学硕士学位。他喜欢合气道和非技术性阅读。Siome 住在巴西里约热内卢。可以将评论通过电子邮件发送给他,地址为 siome@visgraf.impa.br。
加载 Disqus 评论