LECTURE 16 ブリンク処理

○ はじめに

点滅するランプのようなブリンク(点滅)描画処理を行う場合、普通まず最初に思い付くのは同じ描画対象を別の色で再描画する方法だと思います。この方法の場合、再描画の対象がとても多かったり、描画処理が遅いマシンを使っていたりすると、希望通りの動画が得られない可能性があります。

その代わりに 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 秒毎にウィンドウの背景色が 赤 <---> 黒 と切り替わります。何かキーを押すと終了します。

016blink_red.jpg

↑と↓が交互に切り替わります。

016blink_blk.jpg

--- 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 サーバが起動していなければならないという大きな制限がありますが、ぜひ実験してみて下さい!