X Window System, Client Programming, No.6
XImage
ウィンドウ中に画像を表示するときに、画像を構成する点それぞれについて
「指定した色で点を打つ」関数(たとえばXDrawPoint関数)を呼び出す方法は
あまりよい方法ではありません。
これでは、点の数と同数の大量の描画要求を発生させてしまいます。
そのため
- ネットワークのトラフィックが増大する
- 大量の要求を処理させるのでXサーバの負荷が増大する
という悪影響が生じます。
たとえば、320x240ドットの画像を描画するためには
320×240=76800個の要求がクライアントから
Xサーバに送られ処理されることになります。
実際の動作ではネットワーク上の通信はある程度まとめて
行なわれますが、ネットワークやXサーバにとっては
やはり大きな負担となります。
これに対して、
「クライアント側で画像をピクセル値で表現したデータをまず作成し、
それを一度にまとめてXサーバに送りつける」という方法が考えられます。
ある矩形領域の画像をピクセル値で表現するには
「イメージ(XImage)」構造体を使います。
ピクセル値で表現した画像をXImage構造体に格納しておき、
XPutImage関数を使ってXサーバにまとめて送りつけるのです。
こうすれば上記の悪影響を引き起すことはありません。
画像は高速に表示されます。
- XCreateImage関数 -- XImage構造体を生成する
- XPutPixel関数 --- イメージにピクセル値を入れる
- XGetPixel関数 --- イメージからピクセル値を取り出す
- XPutImage関数 --- イメージを描画する
- XGetImage関数 --- XImage構造体を(描画された画像から)生成する
画像をピクセル値で表現する
RGB画像は「各点の色(RGB値)が順に並んだもの」です。
「この各点の色をピクセル値で表したもの」が
「画像のピクセル値による表現」です。
たとえば320x240の画像において、
- 上半分の0≦y<120の範囲が 青、すなわち(R,G,B)=(0,0,255)、
- 下半分の120≦y<240)の範囲が黄、すなわち(R,G,B)=(255,255,0)
であるすると、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 を指定します。
- XYBitmap --- 画像を深さ1のプレーンとして表現します。
関数XPutImage()はグラフィック・コンテクストの
foregroundおよびbackgroundを使います。
- XYPixmap --- 画像を深さ1のプレーンが depth 個並んだ
ものとして表現します。
depthはXサーバ上のウィンドウのdepthと等しくなければなりません。
関数XPutImage()はグラフィック・コンテクストの
forebgoundおよびbackground を使いません。
- ZPixmap --- 画像を各ドットのピクセル値が順番に並んだものとして
表現します。
depthはXサーバ上のウィンドウのdepthと等しくなければなりません。
関数XPutImage()はグラフィック・コンテクストの
forebgoundおよびbackground を使いません。
まず関数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 と書いて
p2r12@nw.tsuda.ac.jpへ送って下さい。
コンパイルできなかったり、きちんと動作しないものを送っても
提出とは認めませんので注意が必要です。
上記の 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/