X Window System, Client Programming, No.4


(注意)南校舎のSolaris2.Xでは X11 Window System の本当の 置き場所は /usr/local/X11R6.4 です。 しかし、多くの Linux ディストリビューションと設定状況を 合わせるために、 /usr/X11R6.4 および /usr/X11R6 から /usr/local/X11R6.4へ シンボリック・リンクが張られています。 このプリントでは今まで

    /usr/X11R6   ← Linuxの場合
    /usr/X11R6.4 ← 南校舎のSolaris2.Xの場合
としてOS別に説明してきましたが、以後、X11の置き場所として /usr/X11R6 に統一して説明することにします。

X Window Systemにおけるカラーの扱い

色は「光の3原色」である「赤(Red)」、「緑(Green)」、「青(Blue)」 それぞれの明るさを混ぜ合わせたものです。 赤、緑、青それぞれについて256段階(すなわち8bit)の明るさが 区別できるとすると、それらを組合せて表現できる色の数は

    256×256×256 = 16777216 (= 2^24) 通り
となります。 ただし全ての計算機が1600万色を同時に出力できる表示装置を持っている わけではなく、同時に利用できる色数はシステムによって異なります。 最近のパソコンは同時に 1600万色使えるものが多くなってきました。 津田塾大学の南校舎のWorkStationでも同時に1600万色を使うことができます。


カラーマップ

同時発色数が限られたなかで色を最大限に利用するために X Window Systemでは『カラーマップ (colormap)』を使います。 カラーマップとは色を登録した表のことで、表の各項目は カラーセルと呼ばれます。 ある色をカラーマップに登録したときの 「表の何番目に登録されたか(=インデックス)」 がピクセル値で、描画における色の指定はこのピクセル値を 用いて行ないます。 すなわち、X Windowのプログラムではまず使いたい色をカラーマップに 登録しておいて、登録したピクセル値を用いてグラフィックコンテクスト に色を設定し、そのグラフィック・コンテクストを用いて描画するわけです。

カラーマップの取得

デフォルトのカラーマップはDefaultColormap()マクロによって 取得することができます。 デフォルトのカラーマップを使うと1つのカラーマップを 他のアプリケーションと共有することになります。


Colormap DefaultColormap(Display *dpy, int scr)マクロ
    指定したスクリーンに割り当てられているカラーマップのIDを返す

デフォルト・カラーマップの取得
    Display *dpy;
    int scr;
    Colormap cmap;
    ...
    dpy = XOpenDisplay(...);
    scr = DefaultScreen(dpy);
    cmap = DefaultColormap(dpy,scr);

取得したカラーマップに、RGB値で指定する色を割り当てるには XAllocColor()関数を使います。 色の指定には XColor 構造体を用います。

XColor構造体のflagで使う定数 (X11/X.hより抜粋)
/* Flags used in StoreNamedColor, StoreColors */
#define DoRed			(1<<0)
#define DoGreen			(1<<1)
#define DoBlue			(1<<2)

XColor構造体の定義 (X11/Xlib.hより抜粋)
typedef struct {
	unsigned long pixel;
	unsigned short red, green, blue;
	char flags;  /* do_red, do_green, do_blue */
	char pad;
} XColor;

関数
Status XAllocColor(Display *dpy,Colormap cmap, XColor *def);
    カラーマップ中の空いているカラーセルに色を割り当てます。
    割り当てに失敗した場合は0が返されます。

RGB値で色をカラーマップに登録するコード
    XColor color_def;
    ...
    color_def.flags = DoRed | DoGreen | DoBlue;
    color_def.red = 赤の明るさ;
    color_def.green = 緑の明るさ;
    color_def.blue = 青の明るさ;
    if (XAllocColor(dpy,cmap,&color_def) == 0) { /* 色の割り当てに失敗 */ }
    ...
    gcv.foreground = color_def.pixel;      /* 割り当てたピクセル値を使う */
    gc = XCreateGC(dpy,win,GCForeground | ..., &gcv);
    ...

「RGB値」の代わりに「色の名前」を使うには、XParseColor()関数を使って 「色の名前」から「RGB値」に変換します。 XParseColor()関数は、色の名前(たとえば "red"や"pink")以外にも、 RGB値を16進数で表現した文字列 ("#RRGGBB" または "#RRRRGGGGBBBB" の形式) を解釈することができます。

関数
Status XParseColor(Display *dpy, Colormap cmap, char *spec, XColor *def)
    色の名前からRGB値をdefに得ます。
    失敗の場合は0を返します。

色の名前からRGB値への変換
    XColor color_def;
    Colormap cmap;
    char *colorname = "red";
    ....
    if (XParseColor(dpy,cmap,colorname,&color_def) == 0) { エラー }
    /* color_defの中のred,green,blueフィールドにRGB値が設定されている */

ちなみにXサーバが参照する「色の名前」と「RGB値」の対応表は /usr/X11R6/lib/X11/rgb.txt にあります。
 rgb.txt (色の名前とRGB値の対応表)
255 250 250		snow
248 248 255		ghost white
248 248 255		GhostWhite
...
255   0   0		red
255 105 180		hot pink
...
255 255 255		white
  0   0   0		black
...


色を使う簡単なアプリケーション例

色を使う例 x4.cを示します。 x4.cの中では、「ルート・ウィンドウの深さを返す」関数

DisplayDepth(Display *dpy, int scr) 
の返り値を用いて、カラーが使えるか否かを判断しています (値が1の場合はモノクロ、それ以外はカラーが使えると判断します)。


x4.c
#include <stdio.h>
#include <X11/Xos.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>

int main(int argc, char **argv) {
    Display *dpy;
    int scr;
    Window win;
    XSetWindowAttributes xswa;
    char *xservname=NULL;
    GC gc;
    XGCValues gcv;
    char *colorname="red";

    if (argc >= 2) {
	colorname = argv[1];
    }
    if ((dpy=XOpenDisplay(xservname)) == NULL) {
	fprintf(stderr,"can not open %s\n",XDisplayName(xservname));
	exit(-1);
    }
    scr = DefaultScreen(dpy);
    xswa.background_pixel = WhitePixel(dpy,scr);
    xswa.border_pixel = BlackPixel(dpy,scr);
    xswa.event_mask = ExposureMask | ButtonPressMask;
    win = XCreateWindow(dpy,RootWindow(dpy,scr),0,0,320,240,1,
			CopyFromParent,CopyFromParent,CopyFromParent,
			CWBackPixel | CWBorderPixel | CWEventMask, &xswa);
    if (DefaultDepth(dpy,scr) == 1) {
	gcv.foreground = BlackPixel(dpy,scr);
    } else {
	XColor color_def;
	Colormap cmap = DefaultColormap(dpy,scr);
	if (!XParseColor(dpy,cmap,colorname,&color_def)) {
	    fprintf(stderr,"color %s not in database\n",colorname);
	    exit(-1);
	}
	if (!XAllocColor(dpy,cmap,&color_def)) {
	    fprintf(stderr,"all colorcells allocated and read/write\n");
	    exit(-1);
	}
	gcv.foreground = color_def.pixel;
    }
    gcv.background = WhitePixel(dpy,scr);
    gc = XCreateGC(dpy,win,GCForeground|GCBackground,&gcv);
    XMapWindow(dpy,win);
    for (;;) {
	XEvent ev;
	XNextEvent(dpy,&ev);
	switch (ev.type) {
	  case Expose:
	    XClearWindow(dpy,win);
	    XFillRectangle(dpy,win,gc,10,10,100,50);
	    break;
	  case ButtonPress:
	    if (ev.xbutton.button == 3) exit(0);
	    break;
	  default:
	    printf("unknown event %d\n",ev.type);
	    break;
	}
    }
}


x4.cの実行 (指定する色を何種類か変えて起動してみましょう)
[コンパイル]
Solaris2.Xの豺腓
侑詫來髭苳紫窿 ㍗齟臼匐釿跿粤  ㍼  ㌣齟臼匐蛯 ㈹惘㈹齒站續 ㈹銖讀踉晒髭苳浜嘔箪跫竅讚蜒闔鶩緕鬯芍罌
也銛ぢの豺腓
侑詫來髭苳紫窿 ㍗齟臼匐釿跿粤  ㍼  ㌣齟臼匐蛯 ㈹惘右踉晒髭苳浜嘔箪跫竅讚蜒闔鶩緕鬯芍罌

ぢ孫侑詫來髭苳侍闌續ゔ 浜嘔箪跫竅讚蜒闔鶩緕鬯芍罌     ← x4 #ee82ee と同じ (∵   238 130 238   violet)
PROMPT$ <I>x4 tomato</I> <IMG SRC="/local-icons/enter.gif">     ← x4 #ff6347 と同じ (∵   255  99  71   tomato)
PROMPT$ <I>x4 slateblue</I> <IMG SRC="/local-icons/enter.gif">  ← x4 #6a5acd と同じ (∵   106  90 205   slateblue)

Visual

XサーバのVisual(=色の表し方)には以下のクラスがあります。

PseudoColor では、色を使うためにはカラーマップに色を割り当てておく 必要がありました。 しかし TrueColorでは 1600万色が前もって read-only で割り当てられて いますので、これを直接使うことができます。

red, green, blue はそれぞれ 0〜255の値を取るものとして、 TrueColorのXサーバで(red, green, blue)の組み合わせて表現できる 色を画面に表示するには以下のようなコードを書きます。

visual.c
static int getshiftbit(unsigned long mask) {
    int i, n;
    for (i=0; i<sizeof(mask)*8; i++) {
	if (mask & 1) {
	    if (mask == 0xff) return(i);
	    else return(-1);
	}
	mask >>= 1;
    }
    return(-1);
}

int use_truecolor=0;
int nshift_red, nshift_green, nshift_blue;

int main() {
    int red, gree, blue;
    XGCValues xgcv;
    ...
    if (DefaultDepath(dpy,scr) == 24) {
	XVisualInfo xvit, *xvi;
	int n, i;
	xvit.depth = 24;
	xvit.class = TrueColor;
	xvi = XGetVisualInfo(xwin->dpy,VisualDepthMask|VisualClassMask,&xvit,&n);
	if (xvi) {
	    for (i=0; i<n; i++) {
		nshift_red = getshiftbit(xvi->red_mask);
		nshift_green = getshiftbit(xvi->green_mask);
		nshift_blue = getshiftbit(xvi->blue_mask);
		if (nshift_red >= 0 && nshift_green >= 0
		    && nshift_blue >= 0) {
		    use_truecolor=1;
		    break;
		}
		xvi++;
	    }
	}
    }
    ...
    for (;;) {
        ...
        if (use_truecolor) {
            gcv.forground =
		red << nshift_red
		+ green << nshift_green
		+ blue << nshift_blue;
        } else {
            gcv.foreground = (red, green, blue)に似た色のピクセル値;
        }
	XChangeGC(dpy,gc,GCForeground,&gcv);
	...
	XDrawPoint(dpy,win,gc,x,y);
    }
}


ソフトウェア工学演習


課題1

上記のクライアント(x4.c)を動作させてみましょう。

課題2

先週作成した「お絵書きツール」において、 描画する図形の色も指定できるように変更して下さい。 少なくとも "red", "blue", "green", "yellow", "black", "white" という6色は指定できるようにして下さい。 ファイル名は x11.c にしましょう。

(注意) 色をカラーマップに登録するのは、各色ごとに1回だけ 行なうようにして下さい。 イベントを処理するループよりも前で行なっておくとよいでしょう。 ときどき、間違って色が必要になった時点でその色をカラーマップを次々と 登録するようなプログラムを書く人がいますが、そのような プログラムはカラーマップを溢れさせてしまいます。

オプション課題3

描かれた図形を配列かリストに覚えておき、Exposeイベントが送られて きたときに、それまで描かれていた図形を再表示するように変更して 下さい。 ファイル名は x11b.c としましょう。

オプション課題4

24bitカラーのXサーバ上では、任意の色で絵が描けるように 変更して下さい。 ファイル名は x11c.c としましょう。 簡単のため、変更する色はR,G,Bそれぞれの値をキーボードから 読み込むものとします。

提出物

課題が完成したら x11.c を p2r10@nw.tsuda.ac.jp へ送って下さい。 Subject は report として下さい。

オプション課題ができた人は、課題番号に応じて Subjectを option 3 または option 4として x11b.c または x11c.c をp2r10@nw.tsuda.ac.jpに送って下さい。

上記の Email アドレス宛に送ったメイルは http://nw.tsuda.ac.jp/cgi-bin/cgiwrap/p2r10/mailhead でヘッダ部が参照できます。 Emailを送ったあとで、正しく提出されたかどうか確認しておいて下さい。

提出期限は来週木曜日の8:50a.m. です。


Yoshihisa Nitta

http://nw.tsuda.ac.jp/