9 Drawing In A Window

在窗口里绘图可以使用各种绘图函数 - 画点,线,圈,矩形,等等。为了能在一个窗口里绘图,我们首先需要定义各种参数 - 如线的宽度,使用什么颜色,等等。这都需要使用一个图形上下文(GC)。

9.1 Allocating A Graphics Context (GC)

如我们已经提到的,一个图形上下文定义一些参数来使用绘图函数。因此,为了绘制不同的风格,我们可以在一个窗口里使用多个图形上下文。使用函数XCreateGC()可以申请到一个新的图形上下文,如以下例(在这段代码里,我们假设”display”指向一个显示结构,”win”是当前创建的一个窗口的ID):

/* this variable will contain the handle to the returned graphics context. */
GC gc;

/* these variables are used to specify various attributes for the GC. */
/* initial values for the GC. */
XGCValues values = CapButt | JoinBevel;
/* which values in 'values' to check when creating the GC. */
unsigned long valuemask = GCCapStyle | GCJoinStyle;

/* create a new graphical context. */
gc = XCreateGC(display, win, valuemask, &values);
if (gc < 0) {
    fprintf(stderr, "XCreateGC: \n");
}

注意,应该考虑一下变量”valuemask”和”values”的角色。因为一个图形上下文有数量惊人的属性,而且通常我们只需要设置里面的一部分,所以我们需要告诉XCreateGC()什么属性是我们需要设置的,这也就是变量”valuemask”的作用。我们接着就可以通过”values”来指定真正的值。在这个例子里,我们定义了图形上下文里的两个属性:

  1. 当绘制一个多重部分的线时,线在连接时使用”Bevelian”风格

  2. 一条线的终端被画直而不是圆形

其它未指定的属性GC将会使用缺省值。

一旦我们创建了一个图形上下文,我们就可以在各种绘图函数里用它,我们也可以为了适应别的函数来变更它的属性。

/* change the foreground color of this GC to white. */
XSetForeground(display, gc, WhitePixel(display, screen_num));

/* change the background color of this GC to black. */
XSetBackground(display, gc, BlackPixel(display, screen_num));

/* change the fill style of this GC to 'solid'. */
XSetFillStyle(display, gc, FillSolid);

/* change the line drawing attributes of this GC to the given values. */
/* the parameters are: Display structure, GC, line width (in pixels), */
/* line drawing  style, cap (line's end) drawing style, and lines     */
/* join style.                                                        */
XSetLineAttributes(display, gc, 2, LineSolid, CapRound, JoinRound);

如果你想了解全部的图形上下文的属性设定方法,请参考函数XCreateGC()的用户文档。我们在这里为了避免过于复杂只使用了一小部分非常简单的属性。

9.2 Drawing Primitives - Point, Line, Box, Circle…

在我们创建了一个GC后,我们就可以通过GC在一个窗口里使用一系列的Xlib函数,这个函数的集合被称为”绘图的基本元素”。为了简便,让我们通过例子来看一看它们是怎么工作的。这里我们假设”gc”是一个前面创建好的GC,”win”是一个已经创建好的窗口的句柄。

 
/* draw a pixel at position '5,60' (line 5, column 60) of the given window. */
XDrawPoint(display, win, gc, 5, 5);
 
/* draw a line between point '20,20' and point '40,100' of the window. */
XDrawLine(display, win, gc, 20, 20, 40, 100);
 
/* draw an arc whose center is at position 'x,y', its width (if it was a     */
/* full ellipse) is 'w', and height is 'h'. Start the arc at angle 'angle1'  */
/* (angle 0 is the hour '3' on a clock, and positive numbers go              */
/* counter-clockwise. the angles are in units of 1/64 of a degree (so 360*64 */
/* is 360 degrees).                                                          */
int x = 30, y = 40;
int h = 15, w = 45;
int angle1 = 0, angle2 = 2.109;
XDrawArc(display, win, gc, x-(w/2), y-(h/2), w, h, angle1, angle2);
 
/* now use the XDrawArc() function to draw a circle whose diameter */
/* is 15 pixels, and whose center is at location '50,100'.         */
XDrawArc(display, win, gc, 50-(15/2), 100-(15/2), 15, 15, 0, 360*64);
 
/* the XDrawLines() function draws a set of consecutive lines, whose     */
/* edges are given in an array of XPoint structures.                     */
/* The following block will draw a triangle. We use a block here, since  */
/* the C language allows defining new variables only in the beginning of */
/* a block.                                                              */
  {
    /* this array contains the pixels to be used as the line's end-points. */
    XPoint points[] = {
      {0, 0},
      {15, 15},
      {0, 15},
      {0, 0}
    };
    /* and this is the number of pixels in the array. The number of drawn */
    /* lines will be 'npoints - 1'.                                       */
    int npoints = sizeof(points)/sizeof(XPoint);
    /* draw a small triangle at the top-left corner of the window. */
    /* the triangle is made of a set of consecutive lines, whose   */
    /* end-point pixels are specified in the 'points' array.       */
    XDrawLines(display, win, gc, points, npoints, CoordModeOrigin);
  }
/* draw a rectangle whose top-left corner is at '120,150', its width is */
/* 50 pixels, and height is 60 pixels.                                  */
XDrawRectangle(display, win, gc, 120, 150, 50, 60);
/* draw a filled rectangle of the same size as above, to the left of the  */
/* previous rectangle. note that this rectangle is one pixel smaller than */
/* the previous line, since 'XFillRectangle()' assumes it is filling up   */
/* an already drawn rectangle. This may be used to draw a rectangle using */
/* one color, and later to fill it using another color.                   */
XFillRectangle(display, win, gc, 60, 150, 50, 60);

如果你觉得已经抓住使用这些函数的要点,那我们后面的说明就会变得简单。我们将提到其它一些使用相同方法的函数。例如,XFillArc()使用与XDrawArc()相同的参数,但它只画一个圆的内部(相似的,XFillRectangle()只画一个矩形区的内部)。另外还有一个函数XFillPolygon()负责填充一个多边形的内部区域。它使用的参数差不多与XDrawLines()相同。但是要注意,如果提供在矩阵里的最后一个参数所代表的点与第一个点不在同一个位置上,函数XFillPolygon()会添加一条虚拟的线来连接那两个点。这两个函数的另外一个不同是,XFillPolygon()使用一个附加的参数,形状,这个参数可以帮助X服务器来优化它的绘图工作。你可以在手册里找到详细的内容。以上的函数还存在它们的复数绘制版本,命名为XFillArcs()和XFillRectangles()。

以上的说明请参看程序 src/02_simple_drawing.c

注意

绘制事件要在事件循环函数中才能显示绘制图形

while(1){
		XNextEvent(dpy, &ev);
		switch(ev.type){
		case Expose:
			XDrawLine(dpy, win, pen, 0, 0, width, height);
			XDrawLine(dpy, win, pen, width, 0, 0, height);
			break;
		case ConfigureNotify:
			if (width != ev.xconfigure.width
					|| height != ev.xconfigure.height) {
				width = ev.xconfigure.width;
				height = ev.xconfigure.height;
				XClearWindow(dpy, ev.xany.window);
				printf("Size changed to: %d by %d\n", width, height);
			}
			break;
		case ButtonPress:
			XCloseDisplay(dpy);
			return 0;
		}
	}

参考

https://osiris.df.unipi.it/~moruzzi/xlib-programming.html#draw