LECTURE 22 フェードイン/フェードアウト

○ はじめに

今回は、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 についての説明は終わりです。