X-Windowを用いたグラフィックプログラム

LinuxをはじめとするUnix系のOSでは、X-Windowと呼ばれるウインドウシステムを用いて、Windowsのようなグラフィカルなユーザインターフェースを作成したり、画像を扱うプログラムを作成します。



X-Window上で動作する描画ツール Tgif

上図はX-Window上で動作するTgifとよばれる描画ツール(お絵かきソフト)の図です。このような文字以外の画像などをプログラムは、C言語とXlibと呼ばれるX-Windowのライブラリ関数を用いて作成します。しかし、そのプログラムは非常に煩雑で理解するが難しいため、本テキストでは簡単に図形や画像を表示できる描画ライブラリ(教育学部表現情報処理コース古賀研究室所属の12年度入学 大学院生 中谷健一君が作成したものです)を用います。この描画ライブラリはXlibを用いていますが、ライブラリのユーザである皆さんはXlibに関する知識は全く必要とはしません。


■ 7-1 描画ライブラリの準備

描画ライブラリを利用するには、以下のファイルを各自のPCにダウンロードする必要があります。以下のリンクを右クリックしてファイルに保存してください。

これらのファイルはC言語とは違うC++という言語のソースファイルです。しかし、ライブラリのユーザはC++を意識する必要はありません。なお、描画ライブラリのマニュアルはここ(但し、PDFファイル)です。


■ 7-2 サンプルプログラムとコンパイル方法

以下に、描画ライブラリを用いたサンプルプログラムを示しますので、xemacsなどを使って入力してください。このサンプルは、「320x240ドット(1ドットは1点という意味)の大きさで、白の背景色を持つウィンドウを作成し、そのウィンドウ上の指定した座標に赤の点を描画する。」というプログラムです。

#include <stdio.h>
#include "graphic.h"    /* この行は必ず必要です。<graphic.h>ではないことに注意。*/

main()
{
/* 1.ウィンドウ作成 320x240ドットのウィンドウを作成 */
if (GCreateWindow(320, 240, WHITE) == 0) {
    printf("ウィンドウの作成に失敗しました\n");
    exit(1);
}

/* 2.図形の描画(赤い点を10個並べて表示) */
GDrawPoint(100, 50, RED);
GDrawPoint(100, 51, RED);
GDrawPoint(100, 52, RED);
GDrawPoint(100, 53, RED);
GDrawPoint(100, 54, RED);
GDrawPoint(100, 55, RED);
GDrawPoint(100, 56, RED);
GDrawPoint(100, 57, RED);
GDrawPoint(100, 58, RED);
GDrawPoint(100, 59, RED);

/* 3.描画内容の表示(この関数を呼び出すことではじめて表示される)。 */
GMapWindow(); /* ウィンドウ内をクリックして終了 */
}

サンプルのファイル名をsample.cとし、プログラム名をsampleすると、このプログラムをコンパイルする方法は以下のとおりです。コンパイルに使用するコマンドがgccではなくg++となっていることに注意してください(ただし、本テキストのユーザはこの理由を理解する必要はありません)。

g++ -o sample sample.c graphic.cpp XWindow.cpp -lX11 -lm -L/usr/X11R6/lib

各自が自分のプログラムを他の名前で作成する場合は、斜体の部分を変更します。

コンパイル済みのプログラムを実行するには、

./sample

と入力します。すると、以下のようなウィンドウが出力されます。少し見にくいですが、赤い点が10個並んでいると思います。なお、ウィンドウ内をマウスにてクリックするとプログラムは終了します。

この他にも図形の関数は多数存在しますが、詳細はマニュアルを参照してください。以下にその幾つかを用いたサンプルプログラムを挙げておきます。

/**************************************
* グラフィック描画サンプルプログラム *
* *
* programed by nakatani *
**************************************/
#include <stdio.h>
#include <stdlib.h>
#include "graphic.h"

#define WIDTH 800
#define HEIGHT 600

main()
{
    int points[10] = { 450, 50, 530, 80, 500, 130, 400, 120, 380, 70 };

    /* 1.ウィンドウ作成 */
    if (GCreateWindow(WIDTH, HEIGHT, BLACK) == 0) {
    printf("ウィンドウ作成に失敗しました\n");
    exit(1);
    }

    /* 2.お絵描き */
    GFillTriangle(100, 50, 200, 200, 30, 100, CYAN);/* 上の列の三角形 */
    GFillBox(250, 50, 100, 50, BLUE); /* 上の列の四角 */
    GFillPolygon(points, 5, MAGENTA); /* 上の列の多角形 */
    GFillCircle(700, 100, 150, 100, GREEN); /* 上の列の円 */

    GSetLineWidth(3); /* 線の太さを3に設定 */

    GDrawPoint(50, 400, WHITE); /* 下の列の点(非常に見にくい) */
    GDrawLine(100, 400, 150, 550, YELLOW); /* 下の列の直線 */
    GDrawTriangle(200, 400, 250, 500, 150, 450, RED); /* 三角形 */
    GDrawBox(300, 400, 100, 150, MAGENTA); /* 下の列の四角形 */
    GDrawPolygon(points, 5, WHITE); /* 上の列の多角形の白い枠線 */
    GDrawCircle(500, 480, 70, 150, CYAN); /* 下の列の円 */
    GDrawText(WIDTH/2, HEIGHT/2, "Hello X-Window System", YELLOW); /* 文字 */

    /* 3.表示 */
    GMapWindow(); /* ウィンドウをマウスクリックで終了 */
}


演習問題7-1

図形描画関数を用いて、各自好きな画像を描画せよ。


■ 7-3 グラフの描画

つぎに、もう少し複雑なほかのサンプルを挙げていきます。

サイン関数のグラフ

このプログラムはサインカーブを表示します。

1: /**************************************
 2: * サイングラフ描画サンプルプログラム *
 3: * *
 4: * programed by nakatani *
 5: **************************************/
 6: #include <stdio.h>
 7: #include <stdlib.h>
 8: #include <math.h>
 9: #include "graphic.h"
10:
11: #define WIDTH 900 /* ウィンドウの横幅 */
12: #define HEIGHT 480 /* ウィンドウの高さ */
13:
14: main()
15: {
16:     int i;
17:     int old_x, old_y, new_x, new_y;
18:     int ox = WIDTH / 2; /* x軸のオフセット量 */
19:     int oy = HEIGHT / 2; /* y軸のオフセット量 */
20:
21:     /* 1.ウィンドウ作成 */
22:     if (GCreateWindow(WIDTH, HEIGHT, WHITE) == 0) {
23:         printf("ウィンドウ作成に失敗しました\n");
24:         exit(1);
25:     }
26:
27:     /* 2.お絵描き */
28:     GDrawLine(0+20, oy, WIDTH-20, oy, BLUE); /* x軸を描画 */
29:     GDrawLine(ox, 0+20, ox, HEIGHT-20, BLUE); /* y軸を描画 */
30:
31:     /* sinカーブを視覚的に表すため、y軸方向へ拡大(100倍)する */
32:     old_x = -360;
33:     old_y = (int)(sin(-360 * 3.141592 / 180.0) * 100);
34:
35:     for (i=-360-1; i<360; i++) {
36:         new_x = i;
37:         new_y = (int)(sin(i * 3.141592 / 180.0) * 100);
38:
39:         /* ウィンドウの座標系は一般のグラフの座標系と異なり、
40:         y軸は下向きである。そのため、符号を反転させている。 */
41:         GDrawLine(old_x+ox, -old_y+oy, new_x+ox, -new_y+oy, MAGENTA);
42:
43:         old_x = new_x;
44:         old_y = new_y;
45:     }
46:
47:     /* 3.表示 */
48:     GMapWindow(); /* ウィンドウをマウスクリックで終了 */
49: }

このプログラムの出力は以下のようになります。

18行目〜19行目:X-Windowの座標系は左上が原点です(下図)。そのため、グラフの原点をウィンドウの中心に移動するために、描画データにウィンドウの高さ、幅の半分の値を足します。これら足される数をオフセットと呼びます。

31行目〜45行目:sin関数でサインの値を計算しグラフを描画します。その方法は、-359度〜359度の範囲で1度ごとにサイン関数の値を計算し、-359度の点と-358度の点の間を直線をひき、次に-358度の点と-357度の点の間を直線を引き、、、というとことを358度と359度の点の間まで繰り返します。つまり、上記のサインのグラフは細かな直線で構成されているわけです。

33行目:sin関数でサインの値を計算していますが、その引数は「度」ではなく「ラジアン」ですので変換が必要です(a度はa×π/180ラジアンです)。
また、sin関数は2πを周期として繰り返されますが、その振幅は-1〜1です。このままではグラフにしたときに見えにくいのでy方向に100倍して、yの値が1の時にy軸から100ドット離れるようにします。

41行目:X-Windowの座標系は左上が原点です(下図)。そのため、y軸に関しては符号を逆にする必要がでてきます。


演習問題7-2

tan関数のグラフを描画せよ。


■ 7-4 簡単なアニメーション

下記のプログラムは簡単なアニメーションを表示します。

/**************************************
* グラフィック描画サンプルプログラム
*
**************************************/
#include <stdio.h>
#include <stdlib.h>
#include "graphic.h"

#define WIDTH 800
#define HEIGHT 600

main()
{

    int i;

    /* 1.ウィンドウ作成 */
     if (GCreateWindow(WIDTH, HEIGHT, BLACK) == 0) {
        printf("ウィンドウ作成に失敗しました\n");
    exit(1);
    }

    for(i=0;i<80;i++){
        GFillCircle(i*10, 100, 100, 100, GREEN); /* 円を描画 */
        GTimer(100); /* ここまでの描画内容を100ms表示する */
        GFillCircle(i*10, 100, 100, 100, BLACK); /* 円を消す */
    }

    /* 3.表示 */
    GMapWindow(); /* ウィンドウをマウスクリックで終了 */
}

forループ中では以下のようにしてアニメーションを実現しています。

  1. 円を緑で描画して100ms待つ
  2. 円を黒で再描画。背景が黒なので結果的に円が消える。
  3. 座標をずらして円を緑で描画。

演習問題 7-3

円以外の適当な図形が、より複雑な動きをするアニメーションを作成せよ。


■ 7-5 画像の表示

ここでは、写真などの画像データを表示するプログラムについて説明します。下記の図は色の3原色と他の色との関係を表した図です。全ての色は、R, G, Bの色の組合せで表現できます。その際に各色を256段階(8bit)で指定するとすると、256x256x256=16777216色の色が指定可能となります。例えば、赤はR=255, G=0 B =0で、黒はR=0, G=0, B=0、黄色はR=255, G=255, B=0といった具合です。但し、写真などに使われているいわゆる中間色はもう少し細かな組合せが必要となります。ここを見てください。左のスライダを動かすと色が変わってウィンドウの上部分にその色のRGBの値が表示されるはずです。

これら、RGBの値を指定することで1600万色もの表現が可能なわけですから、写真や画像を小さな点の集まりとして考え、1つづつ点の色を指定しながら表示していけば、画像が表示できるわけです。

下記のプログラムは、画像ファイルを表示するプログラムです。画像ファイルのサンプルはここからダウンロードしたデータを利用してください。

 1:/**********************************
 2: * 画像表示サンプルプログラム *
 3: * *
 4: * programed by nakatani *
 5: **********************************/
 6:#include <stdio.h>
 7:#include <stdlib.h>
 8:#include "graphic.h"
 9:
10:#define WIDTH 640
11:#define HEIGHT 480
12:
13:void DrawImage(int width, int height, unsigned char *dt);
14:
15:main()
16:{
17:     char fname[80];
18:     unsigned char data[WIDTH*HEIGHT*3];
19:     FILE *fp;
20:
21:     printf("image file name : ");
22:     scanf("%s", fname);
23:
24:     if ((fp = fopen(fname, "r")) == NULL) {
25:        printf("画像ファイル\"%s\"はオープンできません\n", fname);
26:        return 1;
27:     }
28:
29:     fread(data, WIDTH*HEIGHT*3, 1, fp);
30:     fclose(fp);
31:
32:     /* 1.ウィンドウ作成 */
33:     if (GCreateWindow(WIDTH, HEIGHT, BLACK) == 0) {
34:        printf("ウィンドウ作成に失敗しました\n");
35:        exit(1);
36:     }
37:
38:     /* 2.お絵描き */
39:     DrawImage(WIDTH, HEIGHT, data);
40:
41:     /* 3.表示 */
42:     GMapWindow(); /* ウィンドウをマウスクリックで終了 */
43:    }
44:
45:/*-----------------
46: * 画像を表示する *
47: -----------------*/
48:void DrawImage(int width, int height, unsigned char *dt)
49:{
50:     /* 注意:24bit画像のみ動作し
51:     それ以外の画像では動作しない */
52:     int i, j;
53:     unsigned char r, g, b;
54:
55:     for (j=0; j<HEIGHT; j++) {
56:        for (i=0; i<WIDTH; i++) {
57:            r = *dt++;
58:            g = *dt++;
59:            b = *dt++;
60:
61:         GDrawPoint(i, j, RGB(r, g, b));
62:        }
63:
64:        GTimer(0); /* ここまでの描画内容を即座に表示する */
65:     }
66:}

29行目:ファイル中にある画像データを読み込みます。画像データは1ドット(1点)につき、RGB(光の三原色)のデータをそれぞれ値として持っていますので、1ドットに付き3つのデータが必要になります。したがって、画像データは画像のドット数x3の要素数を持った配列に読み込む必要があります。このプログラムは640X480ドット(いわゆるVGA)の画像を表示するプログラムですが、ファイルは、一番左上の1点(1行目の1番目の点)のR,G,Bの各データ、次に一つ右(1行目の2番目の点)のR,G,Bのデータ、、、、1行目の640番目のR,G,Bのデータ、つぎに、2行目の1番目の点、2行目の2番目の点、、、2行目の640番目の点、というフォーマットになっています。

61行目:29行目で読み込んだ配列から1点分のR、 G、Bのデータを取り出して、1点を表示しています。これを、画像のドット数分だけ繰り返せば画像全体が表示されます。


演習問題 7-4

ここから選んだ画像と、これまでに説明した機能(描画、アニメーション)を用いて、何らかの描画を行うプログラムを作成せよ。


レポート課題7-1

演習問題12で作成したプログラムは描画する要素(三角形や四角など)のデータが替わるたびにプログラムをコンパイルする必要がある。これでは、非常に使いにくいので、描画する要素のデータが変更されてもプログラムをコンパイルする必要がないようにプログラムを変更せよ。

具体的には以下のようにする。

GFillTriangle 100 50 200 200 30 100 CYAN
GFillBox 250 50 100 50 BLUE
GFillPolygon 450 50 530 80 500 130 400 120 380 70 5 MAGENTA
GFillCircle 700 100 150 100 GREEN
GSetLineWidth 3
GDrawPoint 50 400 WHITE
GDrawLine 100 400 150 550 YELLOW
GDrawTriangle 200 400 250 500 150 450 RED
GDrawBox 300 400 100 150 MAGENTA