X11: グラフィック

グラフィック
X のグラフィック関数で描画を行う場合は、前景色・背景色・線のスタイルなど、様々なリソースや設定を使って描画を行うため、それらは GC (グラフィックスコンテキスト) というリソースにまとめて保存した上で、使用します。

GC は X のリソースなので、他のクライアントが作成した GC を参照することもできますが、GC を共有して使用することは推奨されていません。

グラフィック操作は、ウィンドウまたは Pixmap を対象にして実行でき、それらはドローアブル (Drawable) と呼ばれます。
引数の型が Drawable (XID) の場合、ウィンドウか Pixmap を指定できるということです。
GC 関数
GC XCreateGC(Display *display, Drawable d, unsigned long valuemask, XGCValues *values);

void XFreeGC(Display *display, GC gc);

XCreateGC で新しい GC を作成し、XFreeGC で GC を解放します。

XGCValues 構造体については、説明を省略します。
valuemask を 0、values を NULL にすると、デフォルト設定の GC を作成できます。

作成した GC は、d で指定されたドローアブルと同じルートウィンドウ・同じ深さを持つ対象に描画する時に使用できます。
イベント
ExposureMask イベントマスクを選択すると、ウィンドウの再描画が必要なタイミングで、Expose イベントが生成されます。

ウィンドウが非表示の状態から画面に表示された時や、他のウィンドウの下に隠れた状態から、一部や全体が見えるようになった時、ウィンドウサイズが変更された時などに来ます。

typedef struct {
 int           type;
 unsigned long serial;
 Bool          send_event;
 Display       *display;
 Window        window;
 int           x, y;
 int           width, height;
 int           count;
} XExposeEvent;

x,y,width,heightウィンドウの原点を基準とした相対位置で、再描画が必要な範囲が指定されます。
countこのイベントの後に続く Expose イベントの数。
count が 0 の場合、このウィンドウに対して、これ以上 Expose イベントは続きません。
count がゼロ以外の場合、このウィンドウには、count 以上の Expose イベントが続きます。

Expose イベントが複数来た場合、それぞれの範囲で複数回更新するか、各範囲を合成した上でまとめて更新するかは、それぞれの判断で決めてください。
プログラム
「黒の背景」「青の四角形塗りつぶし」「赤の直線」を順に描画します。
マウスボタンを押すと終了します。

$ cc -o run 18-graphic.c util.c -lX11

18-graphic.c
#include <X11/Xlib.h>
#include "util.h"

int main(int argc,char **argv)
{
    Display *disp;
    XSetWindowAttributes attr;
    Window win;
    XEvent ev;
    GC gc;
    int width,height;
    
    disp = XOpenDisplay(NULL);
    if(!disp) return 1;

    set_display(disp);

    //ウィンドウ作成

    width = 200, height = 200;

    attr.background_pixel = 0;
    attr.event_mask = ButtonPressMask | ExposureMask | StructureNotifyMask;

    win = XCreateWindow(disp, DefaultRootWindow(disp),
        0, 0, width, height, 0,
        CopyFromParent, CopyFromParent, CopyFromParent,
        CWBackPixel | CWEventMask, &attr);

    //

    gc = XCreateGC(disp, win, 0, NULL);

    XMapWindow(disp, win);

    //イベント

    while(1)
    {
        XNextEvent(disp, &ev);

        switch(ev.type)
        {
            case Expose:
                XClearWindow(disp, win);

                XSetForeground(disp, gc, rgb_to_pixel(0x0000ff));
                XFillRectangle(disp, win, gc, 10, 10, width - 20, height - 20);

                XSetForeground(disp, gc, rgb_to_pixel(0xff0000));
                XDrawLine(disp, win, gc, 0, 0, width - 1, height - 1);
                XDrawLine(disp, win, gc, width - 1, 0, 0, height - 1);
                break;
            case ConfigureNotify:
                width = ev.xconfigure.width;
                height = ev.xconfigure.height;
                if(width < 30) width = 30;
                if(height < 30) height = 30;
                break;
            case ButtonPress:
                goto END;
        }
    }

END:
    XCloseDisplay(disp);

    return 0;
}
解説
ConfigureNotify イベントで常にウィンドウサイズを取得することで、サイズが変更されてもウィンドウ全体を描画できるようにしています。

XSetForeground() は、GC の前景色を変更します。

ところで、ウィンドウマネージャの設定で、ウィンドウサイズの変更中も描画を更新するようになっている場合、ウィンドウサイズを変更している間、ウィンドウ内の画面がちらついているのがわかると思います。

これは、[ ウィンドウの背景を黒で描画→四角形塗りつぶしを描画→直線を描画 ] というように、ウィンドウに対して複数の描画を行っているためです。

通常は、このような画面のちらつきをなくすため、また、ウィンドウ内の更新された範囲だけを再描画できるようにするため、ウィンドウと同じサイズの Pixmap やバッファイメージを用意して、そこに描画を行った後、Expose イベント時や任意のタイミングで、更新範囲のイメージをウィンドウに転送します。
Pixmap
Pixmap は、X グラフィック関数の描画対象として指定できるイメージです。
ただし、イメージバッファを直接操作することはできません。

Pixmap XCreatePixmap(Display *display, Drawable d,
    unsigned int width, unsigned int height, unsigned int depth);

void XFreePixmap(Display *display, Pixmap pixmap);

void XCopyArea(Display *display, Drawable src, Drawable dest, GC gc,
    int src_x, int src_y, unsigned int width, unsigned int height,
    int dest_x, int dest_y);

depth は、d のドローアブルのスクリーンでサポートされている深さであること。
同じ深さのドローアブルに対してのみ、イメージを転送できます。

XCopyArea() で、ウィンドウや他の Pixmap に指定範囲を転送できます。