X Window System, Client Programming, No.6


XImage

ウィンドウ中に画像を表示するときに、画像を構成する点それぞれについて 「指定した色で点を打つ」関数(たとえばXDrawPoint関数)を呼び出す方法は あまりよい方法ではありません。 これでは、点の数と同数の大量の描画要求を発生させてしまいます。 そのため

という悪影響が生じます。
たとえば、320x240ドットの画像を描画するためには 320×240=76800個の要求がクライアントから Xサーバに送られ処理されることになります。 実際の動作ではネットワーク上の通信はある程度まとめて 行なわれますが、ネットワークやXサーバにとっては やはり大きな負担となります。

これに対して、 「クライアント側で画像をピクセル値で表現したデータをまず作成し、 それを一度にまとめてXサーバに送りつける」という方法が考えられます。 ある矩形領域の画像をピクセル値で表現するには 「イメージ(XImage)」構造体を使います。 ピクセル値で表現した画像をXImage構造体に格納しておき、 XPutImage関数を使ってXサーバにまとめて送りつけるのです。 こうすれば上記の悪影響を引き起すことはありません。 画像は高速に表示されます。


画像をピクセル値で表現する

RGB画像は「各点の色(RGB値)が順に並んだもの」です。 「この各点の色をピクセル値で表したもの」が 「画像のピクセル値による表現」です。

たとえば320x240の画像において、

であるすると、RGB画像は次のようになります。
    0   0   255   0   0   255   ...   0   0   255   ← 320x3個の値(0行目の画像)
      ...
    0   0   255   0   0   255   ...   0   0   255   ← 320x3個の値(119行目の画像)
    255 255 0     255 255 0     ...   255 255 0     ← 320x3個の値(120行目の画像)
      ...
    255 255 0     255 255 0     ...   255 255 0     ← 320x3個の値(239行目の画像)

ここで、青、および黄がカラーマップにそれぞれ11番と23番 として登録されていると仮定します。 青色のピクセル値は11、黄色のピクセル値は23となります。 このとき、上記の画像はピクセル値を使って以下のように表せます。

    11   11   ...   11   ← 320個の値(0行目の画像)
      ...
    11   11   ...   11   ← 320個の値(119行目の画像)
    23   23   ...   23   ← 320個の値(120行目の画像)
      ...
    23   23   ...   23   ← 320個の値(239行目の画像)
XImageで使うのはこのような「ピクセル値で表した画像」です。


XImage構造体

/usr/X11R6/include/X11/Xlib.hでXImage構造体は以下のように 定義されています。

|~~~XImage構造体 (X11/Xlib.hより引用)
typedef struct _XImage {
    int width, height;		/* size of image */
    int xoffset;		/* number of pixels offset in X direction */
    int format;			/* XYBitmap, XYPixmap, ZPixmap */
    char *data;			/* pointer to image data */
    int byte_order;		/* data byte order, LSBFirst, MSBFirst */
    int bitmap_unit;		/* quant. of scanline 8, 16, 32 */
    int bitmap_bit_order;	/* LSBFirst, MSBFirst */
    int bitmap_pad;		/* 8, 16, 32 either XY or ZPixmap */
    int depth;			/* depth of image */
    int bytes_per_line;		/* accelarator to next line */
    int bits_per_pixel;		/* bits per pixel (ZPixmap) */
    unsigned long red_mask;	/* bits in z arrangment */
    unsigned long green_mask;
    unsigned long blue_mask;
    ...
} XImage;
|___

formatには/usr/X11R6/include/X11/X.hで定義されている XYBitmap, XYPixmap, ZPixmap を指定します。

まず関数XCreateImage()を使って、初期化したXImage構造体を得た後で、 画像を記憶するために必要なメモリを確保します。

XCreateImage()関数
イメージ構造体を生成します
XImage *XCreateImage(Display *dpy, Visual *visual, unsigned int depth,
        int format, int offset, char *data, unsigned int width,
        unsigned int height, int bitmap_pad, int bytes_per_line)

画像の(x,y)位置のピクセル値を得ます
unsigned long XGetPixel(XImage *ximage, int x, int y)

画像の(x,y)位置のピクセル値を設定します
XPutPixel(XImage ximage, int x, int y, unsigned long pixel)

    bitmap_pad: スキャンラインはこの整数倍の長さになる
    bytes_per_line: スキャンラインの各行の先頭間の間隔
    data: 画像データ
    depth: イメージの深さ
    format: 画像フォーマット (XYBitmap, XYPixmap, ZPixmapのどれか)
    height: 画像の高さ
    offset: スキャンラインの先頭から読み飛ばす長さ
    pixel: ピクセル値を指定
    value: 加算する定数
    visual: Visual構造体
    width: 画像の幅
    ximage: イメージ
    x: x座標
    y: y座標

いろいろな深さのディスプレイに対応したプログラムは以下のようになります。
|~~~
    Display *dpy;
    int scr;
    int depth, width, height, bitmap_pad;
    XImage *ximage;
  
    depth=DefaultDepth(dpy,scr);
    if (depth==1) {
        format = XYPixmap; bitmap_pad=32;
    } else if (depth==8) {
        format = ZPixmap; bitmap_pad = 8;
    } else {
        format = ZPixmap; bitmap_pad = 32;
    }
    ximage = XCreateImage(dpy,DefaultVisual(dpy,scr),
                          depth,format,0,0,width,height,bitmap_pad,0);
    if (ximage == 0) { エラー処理 }
    ximage->data = (char *)malloc(ximage->bytes_per_line * height);
    if (ximage->data == 0) { エラー処理 }
|___

XImage構造体にピクセル値を設定する

イメージ中のあるドットのピクセル値を参照するには関数XGetPixel()を、 設定するには関数 XPutPixel() を使います。

|~~~
    #include <X11/Xutil.h>
    ...
    XImage *ximage;
    int x, y, status;
    long pixel;
    ...
    pixel = XGetPixel(ximage,x,y);
    ...
    status = XPutPixel(ximage,x,y,pixel);
    if (status == 0) { エラー処理 }
|___

XImage構造体で表現した画像を表示する

イメージの矩形領域をウィンドウ(やピックスマップ)に描くには 関数XPutImage()を使います。


ximageの (src_x, src_y) から width*height の領域を dの(dst_x,dst_y)から表示
XPutImage(Display *dpy, Drawable d, GC gc, XImage *ximage,
        int src_x, int src_y, int dst_x, dst_y,
        unsigned int widh, unsigned int height);

|~~~
    #include <X11/Xutil.h>
    ...
    Display *dpy;
    Window win;
    GC gc;
    XImage *ximage;
    int src_x, src_y, dst_x, dst_y, width, height;
    ...
    XPutImage(dpy,win,gc,ximage,src_x,src_y,dst_x,dst_y,width,height);
|___

イメージの参照

ウィンドウ上に表示されている画像を取り出すには関数XGetImage() か関数XGetSubImage()を使います。
関数XGetImage()はウィンドウ上の矩形領域のピクセル値を取り出し、 それを格納した新しいXImage構造体を生成します。 関数XGetSubImage()はウィンドウ上の矩形領域のピクセル値を取り出し、 既に存在するXImage構造体のイメージに格納します。


d の (x,y) から width*height の領域を、指定したformatのイメージ構造体に
して返します。
XImage *XGetImage(Display *dpy, Drawable d, int x, int y,
        unsigned int width, unsigned int height,
        unsigned long plane_mask, int format)

d の (x,y) から width*height の領域を、dst_imageの(dst_x,dst_y)の位置に
コピーします。
XImage *XGetSubImage(Display *dpy, Drawable d, int x, int y,
        unsigned int width, unsigned int height,
        unsigned long plane_mask, int format,
        XImage *dst_image, int dst_x, int dst_y)

|~~~
    Display *dpy;
    Window win;
    GC gc;
    int x, y, dst_x, dst_y, format;
    unsigned int width, height;
    unsigned long plane_mask;
    XImage *ximage;
    ...
    ximage=XGetImage(dpy,win,x,y,width,height,plane_mask,format);
    ...
    ximage=XGetSubImage(dpy,win,x,y,width,height,plane_mask,format,image,dst_x,ds_y);
|___

XImageを使った高速化

x13d.cはドット毎に XDrawPoint()関数を使っていました。 XImage構造体を使って高速化した x14.c は以下のようになります。
x14.c
#include <stdio.h>
#include <X11/Xos.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>

#define WIDTH	320
#define HEIGHT	240

#define RSIZE	4
#define GSIZE	4
#define BSIZE	4

Display *dpy;
int scr;
Window win;
GC gc;
XColor defs[RSIZE][GSIZE][BSIZE];
XImage *image;

XImage *init_ximage(int w,int h) {
    int format, bitmap_pad, depth;
    XImage *image;
    depth = DefaultDepth(dpy,scr);
    if (depth == 1) {
	format = XYPixmap; bitmap_pad = 32;
    } else if (depth == 8){
	format = ZPixmap; bitmap_pad = 8;
    } else if (depth == 16){
	format = ZPixmap; bitmap_pad = 16;
    } else if (depth==24) {
	format = ZPixmap; bitmap_pad = 32;
    } else {
	fprintf(stderr,"unsupported depth %d\n",depth);
	exit(-1);
    }
    image = XCreateImage(dpy,DefaultVisual(dpy,scr),depth,format,0,0,
			 w,h,bitmap_pad,0);
    if (image == 0) {
	fprintf(stderr,"XImage structure allocation failure\n");
	exit(-1);
    }
    if ((image->data =(char *) malloc(image->bytes_per_line * h)) == NULL) {
	fprintf(stderr,"image memory allocation failure\n");
	exit(-1);
    }
    return(image);
}

void init_cmap() {
    Colormap cmap;
    int i,j,k;

    cmap=DefaultColormap(dpy,scr);
    for (i=0; i<RSIZE; ++i) {
	for (j=0; j<GSIZE; ++j) {
	    for (k=0; k<BSIZE; ++k) {
		defs[i][j][k].red=(255 * i / (RSIZE-1)) << 8;
		defs[i][j][k].green=(255 * j / (GSIZE-1))<< 8;
		defs[i][j][k].blue=(255 * k / (BSIZE-1)) << 8;
		defs[i][j][k].flags=DoRed|DoGreen|DoBlue;
		if (!XAllocColor(dpy,cmap,&defs[i][j][k])) {
		    fprintf(stderr,"XAllocColor failed\n");
		    exit(-1);
		}
	    }
	}
    }
}

int init_xwin(int width, int height) {
    XGCValues gcv;
    XSetWindowAttributes xswa;
  
    if ((dpy = XOpenDisplay(NULL)) == NULL) {
	fprintf(stderr, "cant open display\n");
	exit(1);
    }
    scr = DefaultScreen(dpy);
    xswa.background_pixel = BlackPixel(dpy,scr);
    xswa.border_pixel = BlackPixel(dpy,scr);
    xswa.event_mask = ExposureMask | ButtonPressMask;
    win=XCreateWindow(dpy,RootWindow(dpy,scr),0,0,width,height,1,
		      CopyFromParent,CopyFromParent,CopyFromParent,
		      (CWBackPixel|CWBorderPixel|CWEventMask),&xswa);
    init_cmap();
    gcv.foreground = WhitePixel(dpy,scr);
    gcv.background = BlackPixel(dpy,scr);
    gcv.line_width = 1;
    gc = XCreateGC(dpy,win,(GCForeground|GCBackground|GCLineWidth),&gcv);
    XMapWindow(dpy,win);
}

#define N_DITH	4
int bayer[N_DITH][N_DITH] = {
    {  0,  8,  2, 10 },
    { 12,  4, 14,  6 },
    {  3, 11,  1,  9 },
    { 15,  7, 13,  5 }
};

int dither(int d,int csize,int x,int y) {
    int n,e,span;
    span = 255 / csize;
    n = d / span;
    if (n == csize) return(n);
    e = d - n * span;
    if (e * N_DITH * N_DITH
	> bayer[x%N_DITH][y%N_DITH] * span) return(n+1);
    else return(n);
}

void set_rgb(XImage *image,int x0,int y0,unsigned char *data,int w,int h) {
    int x,y;
    unsigned int r,g,b;
    unsigned long red,green,blue;
    unsigned char *s=data;

    for (y=y0; y<y0+h; ++y) {
	for (x=x0; x<x0+w; ++x) {
	    red = *s++; green = *s++; blue = *s++;
	    r = dither(red,RSIZE-1,x,y);
	    g = dither(green,GSIZE-1,x,y);
	    b = dither(blue,BSIZE-1,x,y);
	    XPutPixel(image,x,y,defs[r][g][b].pixel);
	}
    }
}

unsigned char *load_rgb(char *fname,int w,int h) {
    FILE *fp;
    unsigned char *data,*s;
    int i;
    if ((data=(unsigned char *)malloc(w*h*3)) == NULL) {
	fprintf(stderr,"can not malloc %d\n",w*h*3); exit(-1);
    }
    if ((fp=fopen(fname,"r")) == NULL) {
	fprintf(stderr,"can not open %s\n",fname); exit(-1);
    }
    for (i=0,s=data; i<w*h*3; ++i) {
	*s++ = (unsigned char) getc(fp);
    }
    fclose(fp);
    return(data);
}

int main(int argc, char **argv) {
    int ac=argc;
    char **av=argv;
    char *fname="sample.rgb";
    int width=WIDTH, height=HEIGHT;
    unsigned char *data;

    while (--ac) {
	++av;
	if (!strcmp(*av,"-f")) {
	    if (! --ac) goto Usage;
	    fname = *++av;
	} else if (!strcmp(*av,"-w")) {
	    if (! --ac) goto Usage;
	    width = atoi(*++av);
	} else if (!strcmp(*av,"-h")) {
	    if (! --ac) goto Usage;
	    height = atoi(*++av);
	} else {
	Usage:
	    fprintf(stderr,"usage: %s -w width -h height -f filename\n",argv[0]);
	    exit(-1);
	}
    }
    data = load_rgb(fname,width,height);
    init_xwin(width,height);
    image = init_ximage(width,height);
    set_rgb(image,0,0,data,width,height);
    for (;;) {
	XEvent ev;
	XNextEvent(dpy,&ev);
	switch (ev.type) {
	  case Expose:
	    XPutImage(dpy,win,gc,image,0,0,0,0,width,height);
	    break;
	  case ButtonPress:
	    exit(0);
	    break;
	  default:
	    break;
	}
    }
}


x14.cの実行
PROMPT$ <I>gcc -I/usr/X11R6/include x14.c -o x14 -L/usr/X11R6/lib -lX11 -lsocket -lnsl</I> <IMG SRC="/local-icons/enter.gif">
                ←Solaris2.Xの豺
侑詫來髭苳紫窿 ㍗齟臼匐釿跿粤 堪㍼ ㌣齟臼匐蛯 ㈹惘右踉晒髭苳浜嘔箪跫竅讚蜒闔鶩緕鬯芍罌
                ←Linuxなどの豺
侑詫來髭苳侍唄 ㊥ 隰鱧皃踉晒髭苳浜嘔箪跫竅讚蜒闔鶩緕鬯芍罌


演習(11)


課題1

320x240ドットのRGB画像を画面に表示するx14.cの内容を理解し、 実際に動作させて下さい。 (320x240のRGB画像の例は ~nitta/pro2w/tmp.rgbにあります)。

課題2

x14.c を変更して、16x16ドットの小さな画像が (64,128)を左上とする矩形領域に描かれるようにして下さい。 ファイル名は x14a.c としましょう。 (16x16のRGB画像の例は ~nitta/pro2w/face{1,2}.rgb にあります)。
diff -c x14.c x14a.c
*** x14.c	Mon May 22 19:57:26 2006
--- x14a.c	Mon May 22 19:57:50 2006
***************
*** 15,21 ****
  Window win;
  GC gc;
  XColor defs[RSIZE][GSIZE][BSIZE];
! XImage *image;
  
  XImage *init_ximage(int w,int h) {
      int format, bitmap_pad, depth;
--- 15,21 ----
  Window win;
  GC gc;
  XColor defs[RSIZE][GSIZE][BSIZE];
! XImage *image, *image2;
  
  XImage *init_ximage(int w,int h) {
      int format, bitmap_pad, depth;
***************
*** 146,160 ****
  int main(int argc, char **argv) {
      int ac=argc;
      char **av=argv;
!     char *fname="sample.rgb";
      int width=WIDTH, height=HEIGHT;
!     unsigned char *data;
  
      while (--ac) {
  	++av;
  	if (!strcmp(*av,"-f")) {
  	    if (! --ac) goto Usage;
  	    fname = *++av;
  	} else if (!strcmp(*av,"-w")) {
  	    if (! --ac) goto Usage;
  	    width = atoi(*++av);
--- 146,163 ----
  int main(int argc, char **argv) {
      int ac=argc;
      char **av=argv;
!     char *fname="sample.rgb", *fname2="face1.rgb";
      int width=WIDTH, height=HEIGHT;
!     unsigned char *data,*data2;
  
      while (--ac) {
  	++av;
  	if (!strcmp(*av,"-f")) {
  	    if (! --ac) goto Usage;
  	    fname = *++av;
+ 	} else if (!strcmp(*av,"-f2")) {
+ 	    if (! --ac) goto Usage;
+ 	    fname2 = *++av;
  	} else if (!strcmp(*av,"-w")) {
  	    if (! --ac) goto Usage;
  	    width = atoi(*++av);
***************
*** 163,182 ****
  	    height = atoi(*++av);
  	} else {
  	Usage:
! 	    fprintf(stderr,"usage: %s -w width -h height -f filename\n",argv[0]);
  	    exit(-1);
  	}
      }
      data = load_rgb(fname,width,height);
      init_xwin(width,height);
      image = init_ximage(width,height);
      set_rgb(image,0,0,data,width,height);
      for (;;) {
  	XEvent ev;
  	XNextEvent(dpy,&ev);
  	switch (ev.type) {
  	  case Expose:
  	    XPutImage(dpy,win,gc,image,0,0,0,0,width,height);
  	    break;
  	  case ButtonPress:
  	    exit(0);
--- 166,189 ----
  	    height = atoi(*++av);
  	} else {
  	Usage:
! 	    fprintf(stderr,"usage: %s -w width -h height -f filename -f2 filename\n",argv[0]);
  	    exit(-1);
  	}
      }
      data = load_rgb(fname,width,height);
+     data2 = load_rgb(fname2,16,16);
      init_xwin(width,height);
      image = init_ximage(width,height);
+     image2 = init_ximage(16,16);
      set_rgb(image,0,0,data,width,height);
+     set_rgb(image2,0,0,data2,16,16);
      for (;;) {
  	XEvent ev;
  	XNextEvent(dpy,&ev);
  	switch (ev.type) {
  	  case Expose:
  	    XPutImage(dpy,win,gc,image,0,0,0,0,width,height);
+ 	    XPutImage(dpy,win,gc,image2,0,0,64,128,16,16);
  	    break;
  	  case ButtonPress:
  	    exit(0);

課題3

キーボードのキーを押すたびに、16x16ドットの小さな画像が 画面上を動くプログラム x14b.c を作成して下さい。

キー操作動作
h左移動
j下移動
k上移動
l右移動
なるべく計算量が少なくなるプログラムになるよう心がけましょう。
x14b.cの実行
PROMPT$ <I>x14b  -f tmp.rgb  -f2 face1.rgb</I> <IMG SRC="/local-icons/enter.gif">
320x240の大きさの画像 tmp.rgb の紊髻蔚ぢの大きさの画像 face1.rgb が動く

(ヒント) face1.rgbがtmp.rgbからはみ出さないようにしましょう。
(ヒント2) KeyPressイベントの中で XLookupString()をすれば、 入力されたキーがわかります。

幅 w1, 高さ h1 の画像 tmp2.rgb が(x1,y1) から (x2, y2)に動く場合は 以下のようにすればよいでしょう。

    /* まず背景の tmp.rgb (ximage) で tmp2.rgb の場所を塗り潰して */
    XPutImage(dpy,win,gc,ximage,x1,y1,x1,y1,w1,h1);
    /* tmp2.rgb の画像を描く */
    XPutImage(dpy,win,gc,ximage2,0,0,x2,y2,w1,h1);

オプション課題4

課題3で作成したプログラムを、マウスでも操作できるように 変更した x14c.c を作成して下さい。

(ヒント)上下左右ボタンに相当するウィンドウを作って、 そこを押されたら画像を動かす、などの方法があります。

[提出物]
課題が完成したら、x14b.c を Subject: に Report と書いて [email protected]へ送って下さい。 コンパイルできなかったり、きちんと動作しないものを送っても 提出とは認めませんので注意が必要です。

上記の Email アドレス宛に送ったメイルは http://nw.tsuda.ac.jp/cgi-bin/cgiwrap/p2r12/mailhead でヘッダ部は参照できます。 Emailを送ったあとで、正しく提出されたかどうか必ず確認しておいて下さい。

オプション課題はできた人だけが Subject: に Option と書いて x14c.c を同じ宛先に提出して下さい。

提出期限は来週木曜日の 8:50a.m. です。


Yoshihisa Nitta

http://nw.tsuda.ac.jp/