第7章 アニメーション


グラフィックアプリケーションの最大の魅力は絵が動くアニメーションを 表示することができることです。本章ではアニメーションを実現するための 数々の高速描画技法について解説します。


図形を動かす原理

比較的に小さく簡単な形の図形が窓の中をゆっくり動くように見える プログラムを考えます。

動くように見える原理はパラパラ漫画と同じです。ですがグラフィックで パラパラ漫画と同じようにするためには、新しい絵を表示する前に 古い絵を消さなくてはならないので少々厄介です。

図形を消すには、XClearWindow()関数で窓内の全部を消すのでも 良いのですが、消えて欲しい図形が小さい場合には全体を消すのは 冗長です。その図形と同じ形で窓の背景の色を同じ色で塗りつぶす 方が速いでしょう。動く図形がたくさんなら、いちいち塗りつぶす よりも窓全部を消す方が速いでしょう。

つまり、図形を動かすには、背景色で古い図形を描いて塗りつぶして、 図形の色で新しい図形を描くことを繰り返すのです。このため背景の色の GCと図形の色のGCを交互に使いわけることになります。


XORで図形を動かす

ですがここに非常に便利な方法があり、ひとつのGCで図形を消したり 描いたりできるようになります。それはXOR描画と呼ばれる方法です。

XSetFunction( dis, gc, GXxor );

とgcにセットすると、このgcで図形を描こうとすると、図形の各点について 描く前のその点のpixel値と、そのgcの図形のpixel値との排他的論理和(XOR) の値が描いた結果のpixel値として表示されます。

排他的論理和とは、2つの数を2進数で表して、同じ桁のビットごとに 次の規則で演算する規則です。

両者が共に1、あるいは共に0 ならば 結果は 0
両者が互いに異なるならば 結果は 1

例えば2つのpixel値が 10、12ならば

00001010
XOR)00001100

00000110

となります。

このような規則で描画することをXOR描画と呼びます。
図形をXORで描いて、その上に同じ図形を同じ色でXOR描画すると どうなるでしょう。pixel値のビットごとに考えましょう。

もし背景のビットが0ならば、一回目のXOR描画で図形のビットがそのまま 結果のビットとなります。そして二回目のXOR描画で同じビットがXOR されるので結果のビットは0となります。

他方、もし背景のビットが1ならば、一回目のXOR描画で図形のビットが 反転したものが結果のビットとなります。そして二回目のXOR描画で 逆同士のビットがXORされるので結果のビットは1となります。

背景のビットがどちらであれ、二回同じ図形をXORすると 背景のビットは元にもどることがわかります。つまり背景の色が元に もどるのです。これは図形を描いて消したことに他なりなりません。 こうしてひとつのGCで図形を描いたり消したりできるのです。

背景の色のpixel値が0ならば、この上にXORで図形を描くと 図形のそのままの色が表示されます。しかし背景のpixel値が0でなければ 描く結果は別な色となってしまいます。このため図形をXORで描く際には 一工夫が必要で、図形の元のpixel値に背景のpixel値をXORした値を 図形を描く時のGCにセットして、これで図形を描くのです。


図形の並行移動の例

小さな四角が窓の中を走り回るプログラム例を示します。 名前はanime.ccです。是非おためしください。

anime.ccのダウンロード

anime.ccの内容

/* anime.cc */

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <stdio.h>
#include <unistd.h>

#define WIN_WIDTH   256
#define WIN_HEIGHT  256
#define SQUARE_SIZE  16

#define AREA_X_MIN  (0)
#define AREA_X_MAX  (WIN_WIDTH-SQUARE_SIZE)
#define AREA_Y_MIN  (0)
#define AREA_Y_MAX  (WIN_HEIGHT-SQUARE_SIZE)


unsigned long GetColor( Display* dis, char* color_name )
{
    Colormap cmap;
    XColor near_color, true_color;

    cmap = DefaultColormap( dis, 0 );
    XAllocNamedColor( dis, cmap, color_name, &near_color, &true_color );
    return( near_color.pixel );
}


int main( void )
{
    Display* dis;
    Window win;
    XSetWindowAttributes att;
    GC gc;
    XEvent ev;

    int t;
    double x,y,ox,oy;
    double vx,vy;

    dis = XOpenDisplay( NULL );
    win = XCreateSimpleWindow( dis, RootWindow(dis,0), 100, 100,
      WIN_WIDTH, WIN_HEIGHT, 5, WhitePixel(dis,0), BlackPixel(dis,0) );

    att.backing_store = WhenMapped;
    XChangeWindowAttributes( dis, win, CWBackingStore, &att );

    XSelectInput( dis, win, ExposureMask );
    XMapWindow( dis, win );

    do{
        XNextEvent( dis, &ev);
    }while( ev.type != Expose );

    gc = XCreateGC( dis, DefaultRootWindow(dis), 0, 0 );
    XSetFunction( dis, gc, GXxor );
    XSetForeground( dis, gc, BlackPixel(dis,0)^GetColor( dis, "red")  );


    ox = x = WIN_WIDTH/2;
    oy = y = WIN_HEIGHT/3;
    vx = +1.0;
    vy = +1.2;

    XFillRectangle( dis, win, gc, (int)x, (int)y, SQUARE_SIZE, SQUARE_SIZE );

    for( t=0 ; t<2048 ; t++ ){
        usleep(1);

        x += vx;
        y += vy;
	if( x < AREA_X_MIN ){ x=AREA_X_MIN; vx = -vx; }
	if( x > AREA_X_MAX ){ x=AREA_X_MAX; vx = -vx; }
	if( y < AREA_Y_MIN ){ y=AREA_Y_MIN; vy = -vy; }
	if( y > AREA_Y_MAX ){ y=AREA_Y_MAX; vy = -vy; }

        XFillRectangle( dis, win, gc, (int)ox, (int)oy, SQUARE_SIZE, SQUARE_SIZE );
        XFillRectangle( dis, win, gc, (int) x, (int) y, SQUARE_SIZE, SQUARE_SIZE );

        XFlush( dis );

        ox = x;
        oy = y;
    }

    XDestroyWindow( dis , win );
    XCloseDisplay( dis );

    return(0);
}

このプログラムには注目するべきことがいくつもあります。
ますGCに色を設定する時にpixel値を BlackPixel(dis,0)^GetColor( dis, "red") と して背景色のBlackPixel(dis,0)とXOR演算をしています。このGCで図形を描くと期待通り 赤色になります。
ループ内では古い図形にXOR描画して消して、新しい図形をXOR描画して その座標を古い座標に保管することを繰り返しています。 ループに入る前にひとつ新しい図形を描いていることに注意してください。 これがないと最初の図形が永久に残ってしまします。
ループ内ではusleep()関数を使っています。この関数は指定のμ秒だけ 処理を停止します。これがないと図形があまりにも速く移動してしまい 動いているように見えません。includeファイル unistd.hはコンパイラ によってはincludeするとエラーになることもあります。そのような ときはincludeしなくてもかまいません。
ループ内ではXFlush()関数を使って図形の一回の移動ごとにリクエストを Xserverに送っています。これがないとリクエストがバッファに溜ってから Xserverに送られるので動いているようには全く見えません。ただし、 XFlush()関数を連発すると処理が重くなり図形の動きも遅くなります。


その他の高速描画方法

アニメーションをより速く描くにはいろいろな工夫がありますが そのうち代表的なものを紹介します。

  • ピックスマップを使う。

    動く図形をピックスマップに保存しておいて、それを窓にコピーする ことで複雑な図形も短時間に表示させることができます。ここで気を付ける ことはピックスマップを窓にコピーする際に用いるGCに次の設定を しておくことです。

    XSetGraphicsExposures( dis, gc, False );

    GCにこの設定をしておかないと、コピーをするたびにコピー先の 窓にNoExopseイベントがイベントマスクの設定に関係無しに 送られてしまい、正常なイベント処理を妨げてしまいます。

  • 窓全部をピックスマップで作る

    たくさんの図形を動かすようになると、個々の更新作業が順番に 行われているのが見てわかるようになってしまいます。これが不都合な 場合には、窓と同じ大きさのピックスマップを用意して、そこですべての 更新作業を行い完成したらそれをまるごと窓にコピーすることで 滑らかな動きを見ることができます。

  • 動かない図形は更新しない

    描画には時間がかかるので、更新作業の必要の無い図形は極力描かない ようにプログラムを改良するべきです。

  • XFlush()関数、イベント・マウス調査関数は連発しない

    これらの関数は非常に重く、プログラム全体の動きを遅くするばかり でなくCPUを使っている他の人の迷惑となります。連発しないように プログラムを工夫しましょう。

  • Xclient側のメモリに描画する

    もし、Xclient側のCPUが強力または暇であり、かつ画像転送速度が 十分速いなら、Xserverに図を描かせるよりもXclient内で描いて転送する ほうが速いでしょう。この場合、直線や円を描いたりするには自ら その各点の座標を計算することになるので、古典的なアルゴリズム技術 の駆使が必要になるでしょう。


  • 第8章 EPS画像ファイルを出力する
  • 目次
    Copyright(C) by Naoki Watanabe. Oct 21st, 1995.
    渡辺尚貴 naoki@cms.phys.s.u-tokyo.ac.jp