|
○ はじめに
点滅するランプのようなブリンク(点滅)描画処理を行う場合、普通まず最初に思い付くのは同じ描画対象を別の色で再描画する方法だと思います。この方法の場合、再描画の対象がとても多かったり、描画処理が遅いマシンを使っていたりすると、希望通りの動画が得られない可能性があります。
その代わりに Xlib を使ったプログラミングでは、カラーマップ内のカラーセルの RGB 値を書き換えることで、そのカラーセルを参照している描画色を動的に変更することができます。この方法を使えば同じ色を使っている多数の描画対象の色を同時に変更することができます。
ここではカラーセルの書き換えを行う方法について説明していきます。
○ サンプルプログラム
カラーセルの書き換えを使って、ウィンドウの背景色を黒/赤の 2 色で切替えるサンプルプログラム xblink.c です。はじめに のページから、全てのサンプルプログラムを固めた tar ボールをダウンロードすることもできます。
このサンプルプログラムを実行するには条件があります。X サーバが PseudoColor もしくは DirectColor のビジュアルで起動していることが必須条件です。
現在 X サーバがどのビジュアルで起動しているかを調べるには、kterm などのコンソールから xdpyinfo を実行します。このコマンドを実行すると、X サーバに関する様々な情報が表示されます。その出力の中から default visual id : と書かれている行を見付けて、visual id がいくつになっているかを調べます。visual id が分かったら、その id に対応するビジュアルの情報を探します。対応するビジュアル情報の中に class という記述がありますが、そこに PseudoColor、TrueColor などのビジュアルクラスが記述されています。
下は xdpyinfo の出力例の一部です。
default visual id: 0x22 visual: visual id: 0x22 class: TrueColor depth: 16 planes available colormap entries: 64 per subfield red, green, blue masks: 0xf800, 0x7e0, 0x1f significant bits in color specification: 6 bits
上の例だと、default visual id が 0x22 であり、対応するビジュアルのクラスは TrueColor であることが分かります。このディスプレイだと xblink.c の内容は実行できません。X サーバを 8 bit モードで再起動すればおそらく PseudoColor で起動すると思いますので、やってみて下さい。DirectColor モードで起動する方法は残念ながら僕には分かりません。
あと、PseudoColor もしくは DirectColor で起動したとしても、表現できる色数を全て使用しているようなウィンドウマネージャを使っている場合、後述の XAllocColorCells() によるカラーセルの確保に失敗する可能性があります。その場合は twm とかの色数少なそうなウィンドウマネージャで実験してみて下さい( DirectColor では無いと思いますが、PseudoColor だと 256 色なのであり得る)。
xblink.c をコンパイルして PseudoColor もしくは DirectColor 環境で 実行すると、1 秒毎にウィンドウの背景色が 赤 <---> 黒 と切り替わります。何かキーを押すと終了します。
↑と↓が交互に切り替わります。
--- xblink.c ---
1 #include <X11/Xlib.h> 2 #include <X11/Xutil.h> 3 #include <stdio.h> 4 #include <time.h> 5 6 7 #define WIN_W 400 /* ウィンドウの幅 */ 8 #define WIN_H 300 /* ウィンドウの高さ */ 9 #define WIN_X 100 /* ウィンドウ表示位置(X) */ 10 #define WIN_Y 100 /* ウィンドウ表示位置(Y) */ 11 #define BORDER 2 /* ボーダの幅 */ 12 13 14 static Display* dpy; /* ディスプレイ */ 15 static Window root; /* ルートウィンドウ */ 16 static Window win; /* 表示するウィンドウ */ 17 static int screen; /* スクリーン */ 18 static int depth; /* スクリーンの深さ */ 19 static XVisualInfo visual_info; /* ビジュアル */ 20 static Colormap cmap; /* カラーマップ */ 21 static unsigned long color_pixel; /* 切替え色に使用するピクセル値 */ 22 static XColor color[2]; /* 切替えに使う色( RGB ) */ 23 24 25 static void xblink_init( void ); 26 static int xblink_check_visual( void ); 27 static void xblink_create_window( void ); 28 static int xblink_alloc_color( void ); 29 static void xblink_event_loop( void ); 30 31 32 int 33 main( void ) 34 { 35 xblink_init(); 36 37 /* ビジュアルをチェックする */ 38 if ( xblink_check_visual() == -1 ) { 39 return 1; 40 } 41 42 /* 読み書き両用カラーセルを割り当て、色を設定する */ 43 if ( xblink_alloc_color() == -1 ) { 44 return 1; 45 } 46 47 xblink_create_window(); 48 XMapWindow( dpy, win ); 49 xblink_event_loop(); 50 51 return 0; 52 } 53 54 55 static void 56 xblink_init( void ) 57 { 58 dpy = XOpenDisplay( "" ); 59 60 root = DefaultRootWindow( dpy ); 61 screen = DefaultScreen( dpy ); 62 depth = DefaultDepth( dpy, screen ); 63 cmap = DefaultColormap( dpy, screen ); 64 } 65 66 67 /* 68 * PseudoColor もしくは DirectColor のビジュアル 69 * がサポートされているかどうかチェックする。 70 */ 71 static int 72 xblink_check_visual( void ) 73 { 74 /* XAllocColorCells() のために、読み書き両用のビジュアルが 75 サポートされているかどうかチェック */ 76 if ( ! XMatchVisualInfo( dpy, screen, depth, PseudoColor, &visual_info ) ) { 77 printf( "Writable visual class is not supported.\n" ); 78 return -1; 79 } 80 81 return 0; 82 } 83 84 85 /* 86 * 読み書き両用のカラーセルを 1 個確保し、初期表示色を設定しておく。 87 */ 88 static int 89 xblink_alloc_color( void ) 90 { 91 unsigned long b_pixel[1], p_mask[1]; 92 int i; 93 94 /* 色切替え用に、読み書き両用のカラーセルを 1 つ割り当てる */ 95 if ( XAllocColorCells( dpy, cmap, True, p_mask, 0, b_pixel, 1 ) == 0 ) { 96 printf( "XAllocColorCells() failed\n" ); 97 return -1; 98 } 99 100 /* b_pixel[0] にピクセル値が格納されている。xblink.c では p_mask に意味は無い */ 101 color_pixel = b_pixel[0]; 102 103 if ( ! XParseColor( dpy, cmap, "red", &color[0] ) ) { 104 printf( "XParseColor() for red failed\n" ); 105 return -1; 106 } 107 108 if ( ! XParseColor( dpy, cmap, "black", &color[1] ) ) { 109 printf( "XParseColor() for black failed\n" ); 110 return -1; 111 } 112 113 for ( i = 0; i < 2; i ++ ) { 114 color[i].pixel = color_pixel; 115 color[i].flags = DoRed | DoGreen | DoBlue; 116 } 117 118 /* 初期表示色を赤色に設定 */ 119 XStoreColor( dpy, cmap, &color[0] ); 120 return 0; 121 } 122 123 124 /* 125 * ウィンドウの作成 / グラフィックスコンテキストの作成を行う。 126 */ 127 static void 128 xblink_create_window( void ) 129 { 130 /* ウィンドウを作成する。この時点ではまだ画面に表示されない。*/ 131 win = XCreateSimpleWindow( dpy, root, 132 WIN_X, WIN_Y, WIN_W, WIN_H, BORDER, 133 color_pixel, color_pixel ); 134 135 XSelectInput( dpy, win, KeyPressMask ); 136 } 137 138 139 static void 140 xblink_event_loop( void ) 141 { 142 /* 現在の表示色を示すインデックス */ 143 int current_col = 0; 144 145 /* 最後に表示色を切替えた時刻 */ 146 time_t previous = time( NULL ); 147 148 time_t now; /* 現在時刻 */ 149 XEvent evt; /* イベント構造体変数 */ 150 151 while( 1 ) { 152 /* 153 * まず表示色の切り替え処理を行ってから、イベント 154 * の処理を行う( どちらが先でも良いのだが)。 155 */ 156 157 now = time( NULL ); 158 159 /* 前回切替え時刻より 1 秒経過していたら、表示色を切替える */ 160 if ( now - previous >= 1 ) { 161 if ( current_col == 0 ) { 162 current_col ++; 163 } 164 else { 165 current_col = 0; 166 } 167 168 /* 表示色を切替える */ 169 XStoreColor( dpy, cmap, &color[ current_col ] ); 170 171 /* 切替え時刻を保存 */ 172 previous = now; 173 } 174 175 while ( XPending( dpy ) ) { 176 XNextEvent( dpy, &evt ); 177 178 switch ( evt.type ) { 179 case KeyPress: 180 /* リソースの解放 */ 181 /* 182 * XFreeColors( 183 * dpy, cmap, &color_pixel, 1, planes ); 184 */ 185 XDestroyWindow( dpy, win ); 186 XCloseDisplay( dpy ); 187 return; 188 } 189 } 190 } 191 } 192 193 194 /* End of xblink.c */
○ サンプルプログラムの説明
では、サンプルプログラムについて説明していきます。
- 14 〜 22 行目
ファイル内で使用する変数を宣言しています。color_pixel は、ウィンドウの背景色として使用 する色のピクセル値を格納します。その値に対応するカラーセルの値をプログラム中で動的に変 更することで、ブリンク処理を実現します。
- 32 〜 52 行目
xblink.c の main 関数です。おおまかな処理は、
- 初期処理 ( xblink_init() )
- ビジュアルのチェック ( xblink_check_visual() )
- 読み書き両用カラーセルの割り当て / 色設定 ( xblink_alloc_color() )
- ウィンドウ作成 ( xblink_create_window() )
- ウィンドウのマップ ( XMapWindow() )
- イベントループ ( xblink_event_loop() )
となります。
- 67 〜 82 行目 ( xblink_check_visual() )
カラーセルの値を書き換えるには、読み書き両用のビジュアルクラスがサポートされている必要 があります。XMatchVisualInfo() を使うと、指定した条件に合うビジュアルのうち、ある 1 つ のビジュアルの情報を得ることができます。
Status XMatchVisualInfo( Display* display, int screen, int depth, int class, XVisualInfo* vinfo_return ); depth : スクリーンの深さを指定 class : スクリーンのクラス(ビジュアルクラス)を指定 vinfo_return : 条件に合ったビジュアル情報が格納される
指定した条件に合ったビジュアルのうち、ある 1 つのビジュアルの情報が vinfo_return が示 す変数領域に格納されます。XVisualInfo 構造体は以下のようになっています。
typedef struct { Visual* visual; /* システムで使用する Visual 構造体へのポインタ */ VisualID visualid; /* 通常のアプリケーションでは使用されない */ int screen_num; /* スクリーン番号 */ unsigned int depth; /* スクリーンの深さ */ int class; /* StaticGray、 GrayScale、StaticColor、 PseudoColor、TrueColor、DirectColor のいずれか */ unsigned long red_mask; /* TrueColor もしくは DirectColor の場合、 ピクセル値のどのビットが赤色のカラーマップインデ ックスに対応するかを表すマスク */ unsigned long green_mask; /* 緑色に対して red_mask と同じ役割を果たす */ unsigned long blue_mask; /* 青色に対して red_mask と同じ役割を果たす */ int colormap_size; /* このビジュアルで有効なピクセル値の数 */ int bits_per_rgb; /* カラーセルの RGB 値のビット数 */ } XVisualInfo;
class メンバは StaticGray、GrayScale、StaticColor、PseudoColor、TrueColor、DirectColor のいずれかの定数の値を取るので、この値を参照すればビジュアルのクラスが分かります。
red_mask、green_mask、blue_mask は、TrueColor もしくは DirectColor の場合に使用されま す。この 2 つのビジュアルにおいては、1 つのピクセル値を使って 赤 / 緑 / 青の各色に対し て別々のカラーマップ上のエントリを参照し、その組み合せによって色が決定されます。よって、 ピクセル値のどのビットが各色のカラーセルインデックスに対応するかをこれらのマスクで決定 する必要があります。それ以外のビジュアルにおいては 1 つのカラーセルが 1 色を表現するた め、これらのマスクは不要となります。
指定した条件に合ったビジュアルが見付からなかった場合、XMatchVisualInfo() は 0 を返しま す。見付かった場合は 0 以外の値を返します。
xblink.c では、デフォルトの深さと PseudoColor を指定してビジュアルのサポートをチェッ クし、それが駄目だったら DirectColor を指定してチェックを行っています。いずれのビジ ュアルも見付からなかったら、その時点で終了します。
--- 正直言うと、ここでビジュアルのサポートをチェックしても X サーバがそのビジュアル で起動していなければ後述の XAllocColorCells() に失敗するため、xblink.c ではある意味 不要な処理と言えます。これに関しては後述します。---
- 85 〜 121 行目 ( xblink_alloc_color() )
ここでは、読み書き両用のカラーセルを確保し、初期色の設定を行っています。カラーマップ 上に読み書き両用のカラーセルを確保するには、XAllocColorCells() を使います。
Status XAllocColorCells( Display* display, Colormap colormap, Bool contig, unsigned long plane_masks_return[], unsigned int nplanes, unsigned long pixels_return[], unsigned int npixels ); contig : プレーンが連続でなければならないかどうかを指定 plane_masks_return : プレーンマスクの配列 nplanes : プレーンマスクの配列に返されるプレーンマスクの数を指定 pixels_return : ピクセル値の配列が返される npixels : ピクセルの数を指定
XAllocColorCells() の各引数は、正確な意味を考えると結構難しいです。ここでは読み書き両 用のカラーセルをいくつか確保するためだけに必要な説明のみ行います。npixels に必要なカ ラーセル数を、nplanes に 0 を指定すると、pixels_return 配列に確保されたピクセル値が格 納されます。指定した数のカラーセルの確保に成功した場合は 0 以外の値を、失敗した場合は 0 を返します。
ここで確保したカラーセルの値を動的に書き換えることになりますが、そのためには赤/黒の RGB 値が必要になります。ここでは XParseColor() を使って、"red" および "black" の色名 に対応する RGB 値を問い合わせて color 配列に格納しています。この color 配列の各要素に は、あとで使用するために確保したカラーセルのピクセル値と DoRed | DoGreen | DoBlue の フラグを設定しています。
xblink_alloc_color() の最後の処理として、確保したカラーセルの初期色に "red" の RGB 値 を設定しています。読み書き両用カラーセルに RGB 値を設定するには XStoreColor() を使い ます。
XStoreColor( Display* display, Colormap colormap, XColor* color );
color で示される XColor 型変数に、値を設定したいカラーセルのピクセル値と設定したい RGB 値、どの色成分を設定したいかを表すフラグを設定して実行します。
- 124 〜 136 行目 ( xblink_create_window() )
いつも通り XCreateSimpleWindow() によるウィンドウの作成を行っていますが、ウィンドウの 背景色とボーダ色に xblink_alloc_color() で得たピクセル値( color_pixel に格納 ) を指定 していることに注意して下さい。
- 139 〜 191 行目 ( xblink_event_loop() )
毎度おなじみのイベントループです。通常のイベント処理を行う前に表示色の切替え処理を行っ ています。
ループ毎に time() システムコールによって現在時刻を取得します。取得した現在時刻が前回切 替え時刻から 1 秒以上経過していれば、XStoreColor() を使って color_pixel によって参照さ れるカラーセルの内容を書き換えます。この瞬間に画面上のウィンドウの背景色は切り替わりま す。更に、切替えた時点の時刻を前回切替え時刻に保存しておきます。
X のイベント処理では、LECTURE 4 で取り挙げた XPending() を使ってまず処理可能なイベントの数を取得しています。現在読み込み可能なイベントが無い場 合はイベント処理を飛ばして次のループに飛びます。
KeyPress イベントを拾った場合は、リソースを解放してプログラムを終了しています。コメン トアウトされていますが、新しいリソース解放用関数 XFreeColors() を使っています。
XFreeColors( Display* display, Colormap colormap, unsigned long pixels[], int npixels, unsigned long planes ); pixels : ピクセル値の配列を指定 npixels : ピクセルの数を指定 planes : 解放するプレーンを指定
この関数は、 XAllocColorCells() で確保した読み書き両用セルに限らず今までに紹介した XAllocColor()、XAllocNamedColor() によって確保された読み出し専用セルの解放にも使用しま す。ただし、読み書き両用セルは確保したクライアント専用セルであるのに対して、読み出し専 用セルはクライアント間共有であるため、全てのクライアントから解放されるまで実際には再利 用はできません。
なお、XFreeColors() はプログラム中にカラーセルを解放したい場合に使用することとマニュア ルに書かれています。通常はアプリケーションが終了した時点で、そのアプリケーションが確保 したカラーセルは解放されます(だからコメントアウトしている)。
以上で、xblink.c の説明は終わりです。PseudoColor もしくは DirectColor で X サーバが起動していなければならないという大きな制限がありますが、ぜひ実験してみて下さい!