赤、緑、青の原色のうちどれか1つのの色に注目して考えます。 ある原色の明るさが 0 〜 maxlevel の範囲(すなわち maxlevel+1通り)で 表現されているとします。 これを nlevel 通りの明るさにマップしなければならなくなったとすると、 (maxlevel+1)通りが nlevel 通りに対応づけられるので、もとの明るさを (maxlevel+1)/nlevel の長さに分割して考えればよいことになります。 すなわち、元の明るさが x であったとすると、これは 0〜 (nlevel-1) の範囲のうち x * nlevel / (maxlevel+1) 番目にマップされるわけです。
0 〜 (nlevel - 1) の範囲で i番目のレベルが持つ明るさは i * maxlevel/(nlevel-1) です。
maxlevel=255, nlevel=4の場合を考えましょう。
赤、緑、青の各原色についてそれぞれ4段階の明るさに分けると 4^3=64色必要となります。 各原色が0から255までの値を取るとすると、XColor構造体で 指定する値の計算は以下のようになります。
色の割り当て |
#define RSIZE 4 #define GSIZE 4 #define BSIZE 4 Display *dpy; XColor defs[RSIZE][GSIZE][BSIZE]; 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); } } } } } |
(maxlevel+1) 段階で表現された『原色の成分値 x1 』を 別のnlevel段階の値 x2 に変換するには
x2 = x1 × nlevel/(maxlevel+1)を計算します。したがって、赤(red)、緑(green)、青(blue)の値が 与えられたときにどのカラーセルを使って描画すべきかを 計算する式は以下のようになります。
中間色の参照 |
unsigned int r,g,b; XGCValues xgcv; ... r = RSIZE * red/256; g = GSIZE * green/256; b = BSIZE * blue/256; xgcv.foreground = defs[r][g][b].pixel; |
RGBファイルの中で赤緑青の原色値が各8bitで表現されて 並んでいるとします。 すると、320×240のRGB画像を4×4×4=64色で表示する プログラム x12.c は以下のようになります。
x12.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]; 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); } void draw_rgb(unsigned char *data, int w, int h) { int x,y; unsigned int r,g,b; unsigned long red,green,blue; XGCValues xgcv; unsigned char *s=data; for (y=0; y<h; ++y) { for (x=0; x<w; ++x) { red = *s++; green = *s++; blue = *s++; r = RSIZE*red/256; /* ここでディザをかける */ g = GSIZE*green/256; /* ここでディザをかける */ b = BSIZE*blue/256; /* ここでディザをかける */ xgcv.foreground = defs[r][g][b].pixel; XChangeGC(dpy,gc,GCForeground,&xgcv); XDrawPoint(dpy,win,gc,x,y); } } } int main(int argc, char **argv) { int ac=argc; char **av=argv; char *fname="sample.rgb"; int width=WIDTH, height=HEIGHT; FILE *fp; int i; unsigned char *data,*s; 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); } } if ((data=(unsigned char *)malloc(width*height*3)) == NULL) { fprintf(stderr,"can not malloc\n"); exit(-1); } if ((fp=fopen(fname,"r")) == NULL) { fprintf(stderr,"%s: can not open %s\n",argv[0],fname); exit(-1); } for (i=0,s=data; i<width*height*3; ++i) { *s++ = (unsigned char) getc(fp); } fclose(fp); init_xwin(width,height); for (;;) { XEvent ev; XNextEvent(dpy,&ev); switch (ev.type) { case Expose: draw_rgb(data,width,height); break; case ButtonPress: exit(0); break; default: break; } } } |
演習用のRGBファイルの例としては ~nitta/pro2w/tmp.rgb が 用意してあります。 しかし、できるだけ自分で画像を用意してRGB形式に変換して 使ってみることを勧めます。
ppmファイルをrgbファイルに変換するには ppm2rgb を使います。ppm2rgbの使用方法 |
PROMPT$<I>~nitta/bin/ppm2rgb < PPMファイル > RGBファイル</I> <IMG SRC="/local-icons/enter.gif"> (例)PROMPT$ <I>~nitta/bin/ppm2rgb < tmp.ppm > tmp.rgb</I> <IMG SRC="/local-icons/enter.gif"> |
giftopnm | gif→pnm |
ppmtogif | ppm→gif |
pnmscale | pnmファイルの大きさ変更 |
cjpeg | jpegファイルへの変換 |
djpeg | jpegファイルからの変換 |
xv | いろいろな形式を扱える |
変換例 [GIFファイル→RGBファイル] giftopnm tmp.gif | ~nitta/bin/ppm2rgb > tmp.rgb[JPEG ファイル→RGBファイル] djpeg -ppm tmp.jpg | ~nitta/bin/ppm2rgb > tmp.rgb
![]()
「多くの色を用いた画像」を少ない色で表現するには、 元の画像上の点の色を「使える色のうちで最も近い色」に 対応づけるだけでは不十分です。 元の画像にあった細かな色の変化が表現できず、 平坦に塗りつぶされてしまった画像になってしまいます。 そうならないためには、中間色を、少ない色を混ぜ合わせて 表現します。すなわち、対応づけられる色を適当な割合で 変化させる (= 『震えさせる』、ditherは「震える」という意味があります) ことにより、ある点の色が遠目には混ざりあって 元の色に近い色に見えるようにするのです。 これをディザ (dither)法といいます。
マップする色に変化を与える方法としては、
ランダム・ディザは、確率的に値を震えさせる方法です。
たとえば、0〜255の値を4段階にマップする場合を考えます。 255/(4-1)=85 なので、以下のようなマッピングを考えることができます。
0 1 2 3 ← 4段階の数値 0 85 170 255 ← 256段階の明るさを4段階にマップ元の明るさが 130 である点は、130 * 3 /255 = 1.5294... なので (整数で考えれば)1 にマップされることになります。 しかし、より原画像に近い色で表現するためには
前もってディザのパターンを用意しておき、点のXY座標によって 誤差の扱いを変える方法をオーダード・ディザといいます。 ディザのパターンは次のBayerマトリクスの4x4のものが実用上 よく利用されています。
ディザ・パターン (Bayerマトリクス) 2x2: 0 2 3 1 4x4: 0 8 2 10 12 4 14 6 3 11 1 9 15 7 13 5
たとえば、0〜255の値を4段階にマップする場合を考えましょう。 255/(4-1)=85 なので、以下のようなマッピングを考えることができます。
0 1 2 3 ← 4段階の数値 0 85 170 255 ← 256段階の明るさを4段階にマップ元の明るさが 130 である点は、130 * 3 /255 = 1.5294... なので (整数で考えれば)1 にマップされることになります。 ランダムディザのところで示した通り、 0〜CMAXの値を CSIZE 段階にマップする場合には 元の明るさ r の点は r * (CSIZE-1)/CMAX にマッピングされます。 したがって誤差は r * (CSIZE-1) /CMAX - [r * (CSIZE-1) /CMAX] となります。
しかし、より原画像に近い色で表現するためには
N×Nのディザパターンが bayer[N][N]で与えられているとすると
誤差 > bayer[x%N][y%N]/(N*N)のときに、マップされる値を +1 します。 すなわち点の位置(座標)によって誤差の扱いを変えることで、 ある確率で上下の色に振り分けるているわけです。
たとえば、0〜255の値を4段階で表すと
0 1 2 3 ← 4段階の数値 0 85 170 255 ← 256段階の明るさを4段階にマップとなります。このとき 130 という明るさを持つ点は 1.5294... にマップされ、 誤差は 1.5294 - 1.0 = 0.5294... となります。 4x4の大きさのディザパターンを使う場合は
0.5429... > dither[x%4][y%4]/16のときだけ、一つ上の値の2にマップすることにします。 計算速度の点からみると、プログラム的には、両辺に 「ディザの大きさ」を掛けて整数で計算する方がよいでしょう。
上記のクライアント(x12.c)を動作させましょう。
中間色を計算のところで Bayer's Matrixによるオーダード・ディザを 使うように変更したプログラムを作って下さい。 ファイル名は x13b.c としましょう。
x13b.c を変更して、誤差の計算を整数で演算するように工夫した プログラム x13d.c を作って下さい。
課題が完成したら x13b.c を p2r11@nw.tsuda.ac.jp へ送って下さい。 Subject は report としましょう
オプション課題ができた人は、 Subjectを option 3 として x13d.c をp2r11@nw.tsuda.ac.jpに送って下さい。
上記の Email アドレス宛に送ったメイルは http://nw.tsuda.ac.jp/cgi-bin/cgiwrap/p2r11/mailhead でヘッダ部が参照できるます。 Emailを送ったあとで、正しく提出されたかどうか確認しておいて下さい。
提出期限は来週木曜日の8:50a.m. です。