LECTURE 5 グラフィックスを描画する

○ はじめに

とりあえずウィンドウを表示することができるようにはなりました。しかし何も表示されないウィンドウしか作れないのでは、楽しくもなんともありません。次はウィンドウにいろいろなグラフィックスを描いてみたいと思うのが普通でしょう!

ここでは表示されたウィンドウに対して様々なグラフィックスの描画を行う方法について説明していきます。

○ サンプルプログラム

緑色で塗りつぶした円と、赤色で塗りつぶした長方形を描画するサンプルプログラム xgraphics.c です。xwin.c を基本として、必要な修正を加えています。はじめに のページから、全てのサンプルプログラムを固めた tar ボールをダウンロードすることもできます。

xwin.c の時と同様にコンパイルして実行すると、赤色の長方形、緑色の円が描画されたウィンドウが表示されます。何かキーを押すとプログラムが終了します。

005graphics.jpg

--- xgraphics.c ---

    1	#include <X11/Xlib.h>
    2	
    3	#define WIN_W     400  /* ウィンドウの幅   */
    4	#define WIN_H     300  /* ウィンドウの高さ */
    5	#define WIN_X     100  /* ウィンドウ表示位置(X) */
    6	#define WIN_Y     100  /* ウィンドウ表示位置(Y) */
    7	#define BORDER    2    /* ボーダの幅      */
    8	#define BOX_W     200  /* 描画する長方形の幅 */
    9	#define BOX_H     100  /* 高さ */
   10	#define BOX_X     50   /* 左上角の X 座標 */
   11	#define BOX_Y     50   /* 左上角の Y 座標 */
   12	#define CIRCLE_W  100  /* 円が内接する正方形の幅 */
   13	#define CIRCLE_H  100  /* 円が内接する正方形の高さ */
   14	#define CIRCLE_X  200  /* 円の中心の X 座標 */
   15	#define CIRCLE_Y  180  /* 円の中心の Y 座標 */
   16	
   17	
   18	static void draw( Display* dpy, Window win, GC gc, 
   19	  unsigned long green, unsigned long red );
   20	
   21	
   22	int 
   23	main( void )
   24	{
   25		Display*       dpy;         /* ディスプレイ */
   26		Window         root;        /* ルートウィンドウ */
   27		Window         win;         /* 表示するウィンドウ */
   28		int            screen;      /* スクリーン */
   29		unsigned long  black,white; /* 黒と白のピクセル値 */
   30		GC             gc;          /* グラフィックスコンテキスト */
   31		XEvent         evt;         /* イベント構造体 */
   32	
   33		Colormap       cmap;        /* カラーマップ */
   34		XColor         color, exact;
   35		unsigned long  green, red;  /* 緑と赤のピクセル値 */
   36	
   37	
   38		/* Xサーバと接続する */
   39		dpy = XOpenDisplay( "" );
   40	
   41	
   42		/* ディスプレイ変数の取得 */
   43		root   = DefaultRootWindow( dpy );
   44		screen = DefaultScreen( dpy );
   45	
   46	
   47		/* XAllocNamedColor() のためにカラーマップを取得 */
   48		cmap   = DefaultColormap( dpy, screen ); 
   49	
   50	
   51		/* 白、黒のピクセル値を取得 */
   52		white  = WhitePixel( dpy, screen );
   53		black  = BlackPixel( dpy, screen );
   54	
   55	
   56		/* 緑と赤のピクセル値を取得 */
   57		XAllocNamedColor( dpy, cmap, "green", &color, &exact );
   58		green = color.pixel;
   59	
   60		XAllocNamedColor( dpy, cmap, "red", &color, &exact );
   61		red = color.pixel;
   62	
   63	
   64		/* ウィンドウを作成する。この時点ではまだ画面に表示されない。*/
   65		win = XCreateSimpleWindow( dpy, root,
   66			   WIN_X, WIN_Y, WIN_W, WIN_H, BORDER, black, white);
   67	
   68	
   69		/* グラフィックスコンテキストを作る */
   70		gc = XCreateGC( dpy, win, 0, NULL );
   71	
   72	
   73		/* Xサーバから通知してもらうイベントを指定
   74		   ここではキー押下イベントの他に Expose イベントを選択している */
   75		XSelectInput( dpy, win, KeyPressMask | ExposureMask );
   76	
   77	
   78		/* ウィンドウを画面に表示する(マップする)。 */
   79		XMapWindow( dpy, win );
   80	
   81	
   82		/* Xサーバからイベントを読み込んで随時処理していくイベントループ */
   83		while( 1 ) {
   84			XNextEvent( dpy, &evt );
   85	
   86			switch( evt.type ) {
   87				case Expose:      
   88					if ( evt.xexpose.count == 0 ) {
   89						draw( dpy, win, gc, green, red );
   90					}
   91					break;
   92	
   93				case KeyPress:
   94					/* リソースの解放 */
   95					XFreeGC( dpy, gc );
   96					XDestroyWindow( dpy, win );
   97					XCloseDisplay( dpy );
   98					return 0;
   99			}
  100		}
  101	}
  102	
  103	
  104	static void
  105	draw( Display* dpy, Window win, GC gc, 
  106	      unsigned long green, unsigned long red )
  107	{
  108		/* 描画色に赤を設定 */
  109		XSetForeground( dpy, gc, red );
  110	
  111		/* 四角形の描画 */
  112		XFillRectangle( dpy, win, gc, BOX_X, BOX_Y, BOX_W, BOX_H );
  113	
  114		/* 描画色に緑を設定 */
  115		XSetForeground( dpy, gc, green );
  116	
  117		/* 円の描画 */
  118		XFillArc( dpy, win, gc, 
  119			  CIRCLE_X, CIRCLE_Y, CIRCLE_W, CIRCLE_H, 
  120			  0, 360 * 64 );
  121	}
  122	
  123	
  124	/* End of xgraphics.c */

○ サンプルプログラムの説明

では、サンプルプログラムについて説明していきます。これまでに説明した内容については説明を省略します。

  • 8 〜 15 行目

    ウィンドウに描画する長方形および円についての定数を定義しています。これらの定数は、描画 関数内で使用します。

  • 57 〜 61 行目

    緑と赤色のピクセル値を取得しています。xcolor.c の時と同様に XAllocNamedColor() を使って ピクセル値を取得しています。

  • 69 〜 70 行目

    グラフィックスコンテキスト( GC )を生成しています。GC とは、描画に必要な属性情報(描画に 使う色、フォント、線の太さ、etc.)を格納するために必要なものです。これについては LECTURE 6で詳しく説明します。

  • 73 〜 75 行目

    イベントを選択していますが、これまでの KeyPressMask (キー押下イベント用イベントマスク) の他に、ExposureMask ( Expose イベント用イベントマスク )が指定されています。Expose イ ベントについてはLECTURE 7で詳しく説明します。ここでは、 グラフィックスの描画に必要だと考えておけば良いです。

  • 87 〜 91 行目

    イベントループ内で、Expose イベントに対する処理を行っています。最初に

         if ( evt.xexpose.count == 0 ) 
    
    

    という条件判断文がありますが、これは複数連続で Expose イベントが送られて来た場合に、最 後の一つを受け取った時に描画を行うための if 文です。何故このようなことをするのかについ ては、LECTURE 7において Expose イベントの説明をする時に 述べます。

    実際の描画は、後述の draw() 関数で行っています。

  • 95 行目

    リソース解放の部分に、新たに GC 解放用の処理 XFreeGC() が追加されています。これについ ても、GC の説明の際に説明します。

  • 104 〜 121 行目

    Expose イベント受信時の描画処理です。

    XFillRectangle() で長方形を、XFillArc() で円を描画していますが、それぞれの関数を実行 する前に XSetForeground() を使って GC に描画する色を設定しています。グラフィックスを 描画する場合は、必要な描画属性を GC にあらかじめ設定しておいて、その GC を使って描画関 数を実行するという手順になります。XFillRectangle() や XFillArc() については、後程 X の 描画関数をまとめて紹介する際に説明します。XSetForeground() については、 LECTURE 6で GC の説明をする時に説明します。

X の描画関数と GC の説明をまとめて行うと分量が大きくなってしまうので、ここでは 描画関数に関する説明を行い、GC についてはLECTURE 6で行います。グラフィックスの描画には GC が必要なんだということをここでは覚えておいて下さい。

○ Drawable

ところで、様々なグラフィックスを描画する際にはそれらを描画する対象が当然必要となります。Xウィンドウシステムでは、描画の対象となるものとして今までにも出て来た "ウィンドウ" 以外に、"ピクスマップ" と呼ばれるものを用意しています。

ウィンドウとは、画面上の長方形領域のことを表しています。これに対して、ピクスマップは画面上にない(メモリ上、またはオフスクリーン上という)長方形領域のことを表しています。この 2 つの描画対象を総称して Drawable と呼びます。ほとんどの描画関数は、ウィンドウとピクスマップの双方に対して描画を行うことができます。

ピクスマップの使い方の一例ですが、アニメーションなどを行う場合に各フレームをウィンドウに直接描画していてはちらつきが生じるので、フレームをピクスマップに完全に描画してからウィンドウに一気にコピーすることでちらつきを抑える(ダブルバッファリング)ために使ったりします。

○ X の描画関数の説明

xgraphics.c では長方形および円を描きましたが、線を引いたり、多角形(ポリゴン)を描いたり、あるウィンドウ(ピクスマップ)の内容を一気に別のウィンドウ(ピクスマップ)にコピーしたりといろいろなグラフィックスの描画方法があります。以降は描画用の Xlib 関数について

の順に説明していきます。なお、以降の描画関数ではウィンドウ上での位置座標が必要となりますが、ウィンドウにおける座標系は、左上の角を原点として右方向を正に X 軸、下方向を正に Y 軸となります。

● 点の描画

  1. XDrawPoint();

    指定した座標(x, y)に1ピクセルの点を gc のフォアグラウンドカラーで描画します。

      XDrawPoint(
        Display*   display, /* ディスプレイ変数 */
        Drawable   d,       /* 描画対象 */
        GC         gc,  /* グラフィックスコンテキスト */
        int        x,   /* x座標 */
        int        y    /* y座標 */
      );  
    
    

  2. XDrawPoints();

    一度に複数個の点を描画する場合には、XDrawPoints() を用います。

      XDrawPoints(
        Display*   display,  /* ディスプレイ変数 */
        Drawable   d,       /* 描画対象 */
        GC         gc,      /* グラフィックスコンテキスト */
        XPoint*    points,  /* 点の座標の配列 */
        int        npoints, /* 点の個数 */
        int        mode     /* 描画モード */
      );  
    
    

    この関数は、npoints 個の点を points で指定した座標に描画します。ここで出て来る Xpoint 構造体は次のように定義されています。

      typedef struct {
        short x, y;
      } XPoint; 
    
    

    mode に CoordModeOrigin を指定すると、points で指定した座標は全てウィンドウの原点 からの絶対座標であると解釈されます。CoordModePrevious を指定すると各点はその前の点 からの相対座標で表されていると解釈されます。

● 線の描画

  1. XDrawLine();

    gc のフォアグラウンドカラーを使って、点 1 から点 2 に線を引きます。

      
      XDrawLine(
        Display*   display, /* ディスプレイ変数 */
        Drawable   d,       /* 描画対象 */
        GC         gc,      /* グラフィックスコンテキスト */
        int        x1,      /* 点1のx座標 */
        int        y1,      /* 点1のy座標 */
        int        x2,      /* 点2のx座標 */
        int        y2       /* 点2のy座標 */
      );  
    
    

  2. XDrawLines();

    npoints 個の点を結ぶ折れ線を引きます。

      XDrawLines(
        Display*   display, /* ディスプレイ変数 */
        Drawable   d,       /* 描画対象 */
        GC         gc,      /* グラフィックスコンテキスト */
        XPoint*    points,  /* 始点、終点の配列 */
        int        npoints, /* 始点、終点の個数 */
        int        mode     /* 描画モード */
      );  
    
    

    points, npoints, mode の意味は XDrawPoints() のそれと同じです。

  3. XDrawSegments();

    segments で与えられた 2 点の組に対して、2 点間を線として描画します。

      XDrawSegments(
        Display*   display,  /* ディスプレイ変数 */
        Drawable   d,        /* 描画対象 */
        GC         gc,       /* グラフィックスコンテキスト */
        XSegment*  segments, /* セグメントの配列 */
        int        nsegments /* セグメントの個数 */
      );  
    
    

    各セグメント間は線が描画されません。nsegments には segments に設定されているセグメ ントの個数を指定します。

    ここで出て来る XSegment 構造体は次のように定義されています。

      typedef struct {
        short x1, y1, x2, y2; /* 始点、終点 */
      } XSegment;
    
    

● 長方形、円弧の描画

  1. XDrawRectangle(), XFillRectangle()

    長方形を描画します。

      XDrawRectangle(
        Display*     display,  /* ディスプレイ変数 */
        Drawable     d,        /* 描画対象 */
        GC           gc,       /* グラフィックスコンテキスト */
        int          x,        /* 長方形の左上のx座標 */
        int          y,        /* 長方形の左上のy座標 */
        unsigned int width,    /* 長方形の幅 */
        unsigned int height    /* 長方形の高さ */
      );  
    
      XFillRectangle( display, d, gc, x, y, width, height );
    
    

    指定した(x, y)座標を左上の頂点として幅 width、高さ height の長方形を描画します。 XFillRectangle() は塗りつぶした長方形を描画します。

  2. XDrawRectangles(), XFillRectangles()

    長方形を複数描画するためには XDrawRectangles()、XFillRectangles() を使います。 XFillRectangles() は塗りつぶした長方形を描画します。

      XDrawRectangles(
        Display*     display, /* ディスプレイ変数 */
        Drawable     d,       /* 描画対象 */
        GC           gc,      /* グラフィックスコンテキスト */
        XRectangle*  rects,   /* 長方形の配列 */
        int          nrects   /* 長方形の個数 */
      );  
    
      XFillRectangles( display, d, gc, rects, nrects ); 
    
    

    rects は長方形の配列であり、nrects にはその個数を指定します。ここで出て来る XRectangle 構造体は次のように定義されています。

      typedef struct {
        short          x, y;          /* 左上の座標 */
        unsigned short width, height; /* 幅と高さ */
      } XRectangle;
    
    

  3. XDrawArc(), XFillArc()

    円弧を描画します。円弧は長方形の内接円として座標を指定します。

      XDrawArc(
        Display*      display,/* ディスプレイ変数 */
        Drawable      d,      /* 描画対象 */
        GC            gc,     /* グラフィックスコンテキスト */
        int           x,      /* 外接する長方形の左上の座標 */
        int           y,      /* 同上 */
        unsigned  int width,  /* 長方形の幅 */
        unsigned  int height, /* 長方形の高さ */
        int           angle1, /* 始角 */
        int           angle2  /* 終角 */
      );  
    
      XFillArc( display, d, gc, x, y, width, height, angle1, angle2 );
    
    

    XFillArc() は円弧を塗りつぶします。指定する角度の単位は 1/64 度です。例えば 360 度 は 360 x 64 となります。

  4. XDrawArcs(), XFillArcs()

    複数の円弧を描画する場合には XDrawArcs()、XFillArcs() を使用します。XFillArcs() は円弧を塗りつぶします。

      XDrawArcs(
        Display*   display, /* ディスプレイ変数 */
        Drawable   d,       /* 描画対象 */
        GC         gc,      /* グラフィックスコンテキスト */
        XArc*      arcs,    /* 構造体の配列 */
        int        narcs    /* 配列の個数 */
      );  
    
      XFillArcs( dpy, d, gc, arcs, narcs );
    
    

    ここで出て来る XArc 構造体は次のように定義されています。

      typedef struct {
        short          x, y;
        unsigned short width, height;
        short          angle1, angle2;
      } XArc;
    
    

● ポリゴンの塗りつぶし

  1. XFillPolygon();

    ポリゴン(多角形)を塗りつぶす場合には、XFillPolygon() を使います。

      XFillPolygon(
        Display*   display, /* ディスプレイ変数 */
        Drawable   d,       /* 描画対象 */
        GC         gc,      /* グラフィックスコンテキスト */
        XPoint*    points,  /* 頂点配列 */
        int        npoints, /* 頂点の個数 */
        int        shape,   /* ポリゴンの形 */
        int        mode     /* 描画モード */
      );  
    
    

    points、npoints、mode の意味は XDrawPoints() の引数と同じです。points の最初の点 と最後の点が一致していない場合には、自動的に最初の点と最後の点を結んで塗りつぶして くれます。

    shape にはポリゴンの形を指定します。凸型の場合は Convex、凸型ではなく辺が交差して ない場合は Nonconvex、辺が交差している場合は Complex を指定します。

● 文字列の描画

ここで説明する関数は半角英数字のような 1 バイト文字の描画に用いる関数です。日本語のようなマルチバイト文字列を描画する方法については以降の LECTURE で説明します。

  1. XDrawString();

    文字列を描画します。

      XDrawString(
        Display*   display,/* ディスプレイ変数 */
        Drawable   d,      /* 描画対象 */
        GC         gc,     /* グラフィックスコンテキスト */
        int        x,      /* x座標 */
        int        y,      /* y座標 */
        char*      string, /* 描画する文字列 */
        int        length  /* 描画する文字数 */
      );  
    
    

    この関数は、指定した座標(x, y)を原点として string で指定した文字列を gc のフォアグ ラウンドカラーで描画します。length には string の文字数を指定します。

  2. XDrawImageString();

    引数の意味は XDrawString() と同じです。描画する文字列のバウンディングボックス(その 文字列を囲む長方形)が gc のバックグラウンドカラーで塗りつぶされた上に、gc のフォアグ ラウンドカラーで文字列が描画される点が異なります。

      XDrawImageString(
        Display*   display,/* ディスプレイ変数 */
        Drawable   d,      /* 描画対象 */
        GC         gc,     /* グラフィックスコンテキスト */
        int        x,      /* x座標 */
        int        y,      /* y座標 */
        char*      string, /* 描画する文字列 */
        int        length  /* 描画する文字数 */
      );  
    
    

● ピクスマップの生成、解放

  1. XCreatePixmap();

    ピクスマップを生成します。

      Pixmap XCreatePixmap(
        Display*      display, /* ディスプレイ変数 */
        Drawable      d,       /* ピクスマップを生成する対象となる Drawable */
        unsigned int  width,   /* ピクスマップの幅 */
        unsigned int  height,  /* ピクスマップの高さ */
        unsigned int  depth    /* ピクスマップの深さ */
      );  
    
    

    depthには、通常 DefaultDepth() マクロを用いてディスプレイと同じ深さを指定します。 返却値はウィンドウの生成と同じようにピクスマップの識別子( ID )です。なお深さ 1 の ピクスマップのことを特別にビットマップと呼びます。

  2. XFreePixmap();

    必要のなくなったピクスマップを解放するには XFreePixmap()を用います。

       XFreePixmap( display, pixmap );
    
    

    pixmap には XCreatePixmap() などで生成したピクスマップのIDを指定します。

● 領域のクリア

領域のクリア方法は、クリアする対象(ウィンドウの一部、ウィンドウの全部、ピクスマップ)によって異なります。

  1. XClearArea()

    ウィンドウ内の長方形領域をクリアします。

      XClearArea(
        Display*      display,  /* ディスプレイ変数 */
        Window        w,        /* ウィンドウ */
        int           x,        /* 長方形の左上のx座標 */
        int           y,        /* 長方形の左上のy座標 */
        unsigned int  width,    /* 長方形の幅 */
        unsigned int  height,   /* 長方形の高さ */
        Book          exposures /* Exposeイベントを送るか送らないか */
      );  
    
    

    (x,y)を左上の頂点とし、width、height の大きさを持つ長方形領域をクリア(ウィンドウの バックグラウンドカラーで塗りつぶす)します。exposures には True か False を指定します。 True の場合には領域クリア後 X サーバがクライアントに Expose イベントを送ります。

  2. XClearWindow();

    ウィンドウ全体をクリアします( XClearArea() のように領域を指定する必要はありません)。

      XClearWindow(
        Display*     display, /* ディスプレイ変数 */
        Window       w        /* ウィンドウ */
      );  
    
    

  3. XFillRectangle( display, d, x, y, width, height );

    ピクスマップをクリアするための関数は提供されていないので、長方形の塗りつぶしを使っ てクリアします。

● 領域のコピー

  1. XCopyArea();

    ある drawable の内容を別の drawable にそのままコピーする場合は、XCopyArea() を使い ます。

      XCopyArea(
        Display*      display, /* ディスプレイ変数 */
        Drawable      src,     /* コピー元のdrawable */
        Drawable      dest,    /* コピー先のdrawable */
        GC            gc,      /* グラフィックスコンテキスト */
        int           src_x,   /* コピー領域の左上のx座標 */
        int           src_y,   /* コピー領域の左上のy座標 */
        unsigned int  width,   /* コピー領域の幅 */
        unsigned int  height,  /* コピー領域の高さ */
        int           dest_x,  /* コピー先の起点のx座標 */
        int           dest_y   /* コピー先の起点のy座標 */
      );  
    
    

    src から dest に対して、(src_x, src_y)を左上の頂点として、幅が width で高さが height の長方形領域をコピーします。コピー先は(dest_x, dest_y)を左上の頂点とする長方形領域とい うことになります。src と dest は同じ深さでなくてはいけません。

  2. XCopyPlane();

    深さの異なる drawable 間でコピーする場合には、XCopyPlane()を使います。このとき、コ ピーされるプレーンは1プレーンだけです。

      XCopyPlane(
        Display*       display, /* ディスプレイ変数 */
        Drawable       src,     /* コピー元のdrawable */
        Drawable       dest,    /* コピー先のdrawable */
        GC             gc,      /* グラフィックスコンテキスト */
        int            src_x,   /* コピー領域の左上のx座標 */
        int            src_y,   /* コピー領域の左上のy座標 */
        unsigned int   width,   /* コピー領域の幅 */
        unsigned int   height,  /* コピー領域の高さ */
        int            dest_x,  /* コピー先の起点のx座標 */
        int            dest_y,  /* コピー先の起点のy座標 */
        unsigned long  plane    /* コピーするプレーン */
      );  
    
    

    プレーンマスク( plane )はビットパターンであり、1が設定されているビットに対応するプ レーンを1プレーンだけコピーします。よって 1 を設定するビットは一つだけでなければいけ ません。これ以外の引数の意味は XCopyArea() と同じです。コピーされるプレーンのビット が 1 である部分が gc のフォアグラウンドカラーで、0の部分がバックグラウンドカラーで dest にコピーされます。