|
|
○ はじめに
今回は、Imlib で読み込んだ画像データを使ったフェードイン/フェードアウトを実装してみます。
ただしアルゴリズムの説明と簡単に実行結果を得ることを目的としますので、Shared Memory Extension (XImage や Pixmap をサーバ/クライアント間の共有メモリに置ける様にする。これにより大容量画像データを通信路に乗せる必要が無くなるため、高速化が図れるという拡張)等の高速化は考慮していません。そのため大きな画像では非常に時間がかかってしまう場合がありますが、ご了承ください。
○ フェードイン処理
フェードインは、真っ暗な状態から少しづつ画像を表示していく処理です。これは、ピクセルの値が初期状態では RGB 全て 0(黒色)であり、最終的には表示したい画像の値になれば良いことを意味します。
よって
変換後の値 = ratio * 変換前の値 ※(0.0 <= ratio <= 1.0)
であり、ratio を 0.0 から 1.0 まで少しづつ変化させていけば良いことになります。
○ フェードアウト処理
フェードアウトは、画像を表示している状態から少しづつ暗くしていき、最終的に真っ暗な状態にする処理です。これは、ピクセルの値が初期状態では画像の値であり、最終的には RGB 全て 0(黒色)になれば良いことを意味します。
よって
変換後の値 = ratio * 変換前の値 ※(0.0 <= ratio <= 1.0)
であり、ratio を 1.0 から 0.0 まで少しづつ変化させていけば良いことになります。
○ サンプルプログラム
フェードイン/フェードアウトを実装したサンプルプログラム fade.c です。はじめに のページから全てのサンプルプログラムを固めた tar ボールをダウンロードすることもできます。
前回と同じく、-lImlib オプションを指定してコンパイルしてください。
コンパイルを終えたら、画像ファイル名を引数に与えて実行してみてください。画像が表示されます。上のカーソルキーを押すとフェードイン、下を押すとフェードアウトアニメーションが表示されます。左右を押すと元画像が表示されます。Esc キーを押すと終了します。
本サンプルプログラムは、起動時に元画像をメモリ上に保存します。そのため、大きな画像等を表示すると結構メモリを消費すると思います。また、フェードイン/フェードアウト処理においては高速化やタイミングの同期等を考慮していませんので、画像のサイズによって所用時間が異なります。サイズが大きい場合はかなり遅くなる可能性がありますので、ご了承ください。なお、ゲームやアニメーション等で処理終了までの時間を一定にしたい場合は、1 フレーム表示する毎に所用時間を計測し、早すぎれば待つ/遅すぎたらフレームをスキップする等の実装が必要です。
また、イベントループ内で上下キーを押すとフェードイン/フェードアウト処理を実行しますが、処理が終わるまでイベント処理を行っていないため、ウィンドウが固まった様になると思います。ご了承ください。GUI として実用的にするためには、1 フレーム表示する毎にイベント処理を行う等の実装を考えた方が良いでしょう。
--- フェードイン時(静止画ですが気分だけでも…) ---

↓

↓
--- フェードアウト時(静止画ですが気分だけでも…) ---

↓

↓
なお、fade_in() および fade_out() について、1280x1024 の JPEG 画像を用いて実行した場合にかかった時間を自環境で計測した結果を以下に載せておきます。
| PentiumIII 450MHz memory 512MB |
Xeon 3.60GHz memory 1GB |
|
|---|---|---|
| fade_in() | 19933.406 ms | 3630.223 ms |
| fade_out() | 19844.507 ms | 3671.891 ms |
--- fade.c ---
1 #include <X11/Xlib.h>
2 #include <X11/keysym.h>
3 #include <X11/Xutil.h> /* shape.h の前に必要 */
4 #include <X11/extensions/shape.h> /* Shape Extension */
5 #include <Imlib.h>
6 #include <stdio.h>
7 #include <string.h>
8 #include <sys/time.h>
9 #include <unistd.h>
10
11 static Display* display;
12 static Window window;
13 static ImlibData* imlib_data;
14 static ImlibImage* imlib_image;
15 static Pixmap pixmap;
16 static Pixmap mask;
17 static unsigned char* original;
18 static int width;
19 static int height;
20
21
22 static int save_original( void );
23 static void disable_resize( void );
24 static void render( void );
25 static void update_image( void );
26 static void show_original( void );
27 static void fade_in( void );
28 static void fade_out( void );
29
30
31 int
32 main( int argc, char** argv )
33 {
34 int screen;
35
36 if ( argc <= 1 ) {
37 fprintf( stderr, "画像ファイルを指定してください。\n" );
38 exit(1);
39 }
40
41 display = XOpenDisplay( NULL );
42 screen = DefaultScreen( display );
43
44 if ( ( imlib_data = Imlib_init( display ) ) == NULL ) {
45 fprintf( stderr, "Imlib の初期化に失敗しました。\n" );
46 XCloseDisplay( display );
47 exit(1);
48 }
49
50 if ( ( imlib_image = Imlib_load_image( imlib_data, argv[1] ) ) == NULL ) {
51 fprintf( stderr, "画像の読み込みに失敗しました。\n" );
52 XCloseDisplay( display );
53 exit(1);
54 }
55
56 width = imlib_image->rgb_width;
57 height = imlib_image->rgb_height;
58
59 if ( save_original() != 0 ) {
60 fprintf( stderr, "元画像の保存に失敗しました。\n" );
61 Imlib_kill_image( imlib_data, imlib_image );
62 XCloseDisplay( display );
63 exit(1);
64 }
65
66 window = XCreateSimpleWindow(
67 display,
68 DefaultRootWindow( display ),
69 width,
70 height,
71 width,
72 height,
73 0,
74 BlackPixel( display, screen ),
75 BlackPixel( display, screen )
76 );
77
78 disable_resize();
79 XSelectInput( display, window, StructureNotifyMask | KeyPressMask );
80 XMapWindow( display, window );
81 render();
82 update_image();
83
84 while ( 1 ) {
85 int esc_pressed = 0;
86 XEvent event;
87 KeySym key_sym;
88
89 XNextEvent( display, &event );
90
91 switch ( event.type ) {
92 case KeyPress:
93 XLookupString( &( event.xkey ),
94 NULL, 0, &key_sym, NULL );
95
96 switch ( key_sym ) {
97 case XK_Left:
98 case XK_Right:
99 show_original();
100 break;
101
102 case XK_Up:
103 fade_in();
104 break;
105
106 case XK_Down:
107 fade_out();
108 break;
109
110 case XK_Escape:
111 esc_pressed = 1;
112 break;
113 }
114
115 break;
116 }
117
118 if ( esc_pressed ) {
119 break;
120 }
121 }
122
123 Imlib_free_pixmap( imlib_data, pixmap );
124 Imlib_kill_image( imlib_data, imlib_image );
125 free( original );
126 XDestroyWindow( display, window );
127 XCloseDisplay( display );
128 return 0;
129 }
130
131
132 static int
133 save_original( void )
134 {
135 int size = width * height * 3;
136
137 if ( ( original = ( unsigned char* )malloc( size ) ) == NULL ) {
138 return -1;
139 }
140
141 memcpy( original, imlib_image->rgb_data, size );
142 return 0;
143 }
144
145
146 static void
147 disable_resize( void )
148 {
149 XSizeHints hints;
150
151 hints.min_width = width;
152 hints.min_height = height;
153 hints.max_width = width;
154 hints.max_height = height;
155 hints.flags = PMinSize | PMaxSize;
156
157 XSetWMNormalHints( display, window, &hints );
158 }
159
160
161 static void
162 render( void )
163 {
164 Imlib_render( imlib_data, imlib_image, width, height );
165 pixmap = Imlib_move_image( imlib_data, imlib_image );
166 mask = Imlib_move_mask( imlib_data, imlib_image );
167 }
168
169
170 static void
171 update_image( void )
172 {
173 XSetWindowBackgroundPixmap( display, window, pixmap );
174
175 if ( mask ) {
176 XShapeCombineMask( display, window,
177 ShapeBounding, 0, 0, mask, ShapeSet );
178 }
179
180 XClearWindow( display, window );
181 XSync( display, False );
182 }
183
184
185 static void
186 show_original( void )
187 {
188 memcpy( imlib_image->rgb_data, original, width * height * 3 );
189 Imlib_free_pixmap( imlib_data, pixmap );
190 Imlib_changed_image( imlib_data, imlib_image );
191 render();
192 update_image();
193 }
194
195
196 #define TIMER_DIFF( diff, end, start ) { \
197 (diff)->tv_usec = (end)->tv_usec - (start)->tv_usec; \
198 if ( (diff)->tv_usec < 0 ) {\
199 (diff)->tv_usec += 1000000; \
200 (diff)->tv_sec = (end)->tv_sec - (start)->tv_sec - 1;\
201 }\
202 else {\
203 (diff)->tv_sec = (end)->tv_sec - (start)->tv_sec;\
204 }\
205 if ( (diff)->tv_sec < 0 ) {\
206 (diff)->tv_sec = (diff)->tv_usec = 0;\
207 }\
208 }
209
210 #define MIX_IMAGE( ratio ) { \
211 int row; \
212 int col; \
213 int rgb; \
214 int index; \
215 \
216 for ( row = 0; row < height; row ++ ) { \
217 for ( col = 0; col < width; col ++ ) { \
218 for ( rgb = 0; rgb < 3; rgb ++ ) { \
219 index = ( row * width + col ) * 3 + rgb; \
220 imlib_image->rgb_data[ index ] = ( unsigned char )\
221 ( original[ index ] * ( ratio ) / 100 ); \
222 } \
223 } \
224 } \
225 \
226 Imlib_free_pixmap( imlib_data, pixmap ); \
227 Imlib_changed_image( imlib_data, imlib_image ); \
228 render(); \
229 update_image(); \
230 }
231
232
233 static void
234 fade_in( void )
235 {
236 struct timeval start;
237 struct timeval end;
238 struct timeval diff;
239
240 int ratio = 0;
241
242 printf( "Fade in..." );
243 fflush( stdout );
244 gettimeofday( &start, NULL );
245
246 while ( ratio < 100 ) {
247 MIX_IMAGE( ratio );
248 ratio += 2;
249 }
250
251 gettimeofday( &end, NULL );
252 TIMER_DIFF( &diff, &end, &start );
253 printf( "done ( %ld ).\n", diff.tv_sec * 1000000 + diff.tv_usec );
254 fflush( stdout );
255 }
256
257
258 static void
259 fade_out( void )
260 {
261 struct timeval start;
262 struct timeval end;
263 struct timeval diff;
264
265 int ratio = 100;
266
267 printf( "Fade out..." );
268 fflush( stdout );
269 gettimeofday( &start, NULL );
270
271 while ( ratio > 0 ) {
272 MIX_IMAGE( ratio );
273 ratio -= 2;
274 }
275
276 gettimeofday( &end, NULL );
277 TIMER_DIFF( &diff, &end, &start );
278 printf( "done ( %ld ).\n", diff.tv_sec * 1000000 + diff.tv_usec );
279 fflush( stdout );
280 }
281
282
283 /* End of fade.c */
○ サンプルプログラムの説明
では、サンプルプログラムについて説明していきます。Imlib による画像読み込み/表示、その他いくつかの処理については LECTURE 20 Imlib を使った画像表示 および LECTURE 21 画像のグレースケール/ネガティブ/エッジ検出処理を基本としていますので、そちらを参照してください。
- 31 〜 129 行目 ( main() )
fade.c の main 関数です。各処理について以下で説明していきます。
- 59 〜 64 行目
save_original() という自作関数で、Imlib で読み込んだ元画像データの保存を行っています。詳しくは後程説明します。
- 78 行目
簡単のため、disable_resize() という自作関数でウィンドウサイズを変更できなくしています。LECTURE 21 画像のグレースケール/ネガティブ/エッジ検出処理 のサンプルプログラムと同じなので、そちらを参照してください。
- 97 〜 100 行目
左右のカーソルキーが押された時の処理です。show_original() という自作関数で元画像の表示を行っています。LECTURE 21 画像のグレースケール/ネガティブ/エッジ検出処理 のサンプルプログラムと同じなので、そちらを参照してください。
- 102 〜 104 行目
上のカーソルキーが押された時の処理です。fade_in() という自作関数でフェードイン処理を開始しています。詳しくは後程説明します。
- 106 〜 108 行目
下のカーソルキーが押された時の処理です。fade_out() という自作関数でフェードアウト処理を開始しています。詳しくは後程説明します。
- 123 〜 125 行目
Imlib_free_pixmap() によって Pixmap を解放した後、Imlib_kill_image() を使って ImlibImage 構造体を解放しています。更に、保存しておいた元画像データを解放しています。
- 132 〜 143 行目 ( save_original() )
本関数では、Imlib 内部に格納されている元画像データの保存を行います。グレースケール画像に関する処理を行っていない以外はLECTURE 21 画像のグレースケール/ネガティブ/エッジ検出処理 のサンプルプログラムと同じなので、そちらを参照してください。
- 196 〜 208 行目 ( TIMER_DIFF() )
fade_in() / fade_out() 内で所用時間を表示したいために用意したマクロです。struct timeval 型変数である start と end 間の経過時間を計算して、同じく struct timeval 型変数の diff に格納しています。
- 210 〜 230 行目 ( MIX_IMAGE() )
前述の
変換後の値 = ratio * 変換前の値
に基づいて変換後の全ピクセルの値を計算し、結果画像を表示するマクロです。計算後は
- Imlib_free_pixmap() にて既存の Pixmap を解放対象に設定
- Imlib_changed_iamge() で Imlib に画像データの変更を通知
- 自作関数 render() で Pixmap を生成
- 自作関数 update_image() で Pixmap を表示
を行っています。これについてはLECTURE 21 画像のグレースケール/ネガティブ/エッジ検出処理 のサンプルプログラムと同じなので、そちらを参照してください。
- 233 〜 255 行目 ( fade_in() )
フェードイン処理を行う関数です。246 〜 249 行目で ratio を 0 〜 100 まで変化させながら、毎回 MIX_IMAGE() によって変換/結果表示を行っています。ratio は MIX_IMAGE() 内で 100 で割るため、結果的にピクセルの値を 0 〜 画像のピクセル値に変換しています。
前後の処理は、所用時間を計測して表示しているだけです。
- 258 〜 280 行目 ( fade_out() )
フェードアウト処理を行う関数です。271 〜 274 行目で ratio を 100 〜 0 まで変化させながら、毎回 MIX_IMAGE() によって変換/結果表示を行っています。ratio は MIX_IMAGE() 内で 100 で割るため、結果的にピクセルの値を画像のピクセル値 〜 0 に変換しています。
前後の処理は、所用時間を計測して表示しているだけです。
以上で fade.c についての説明は終わりです。