強火で進め

このブログではプログラム関連の記事を中心に書いてます。

ImageMagick(MagickWand)でアニメーションGIF(Animated GIF)を作成するサンプル

前日紹介した「ImageMagickiPhone用にビルドする為のシェルスクリプト」によりiPhoneImageMagick を使うことが可能に成りました。そこで早速、以前も作った事が有るアニメーションGIF(Animated GIF)を作成するサンプルの ImageMagick 版をやろうと思います。

ImageMagickiPhone用にビルドする為のシェルスクリプト - 強火で進め
http://d.hatena.ne.jp/nakamura001/20100920/1284942306

実は今回のこのネタ、以前に書いた「iPhoneアプリ内でアニメーションGIF(Animated GIF)を作成するサンプル」に頂いたコメントが発端になっています。

iPhoneアプリ内でアニメーションGIF(Animated GIF)を作成するサンプル - 強火で進め
http://d.hatena.ne.jp/nakamura001/20100121/1264070746

このエントリーのコメントにてiOS 4.0でビルド出来ないとの情報を頂きました。自分でもビルドした所、確かにビルドに失敗しました。この時に使った方法では MacPorts でインストールした libpng などを使っていました。

本来であればMacPortsでインストールしたものはMac環境で使う為のものなのでiPhone用としても動作していたのはオマケみたいなものですからこの様な仕様変更が入ってしまうのもしようがないです。

ということで libpng などをソースからビルドしようかと調査をした所、たまたま ImageMagickiPhone用にビルドしていた方を見つけたので「よし、 ImageMagick が有るならそっちで良いか」と成ったわけです。

作り方

ImageMagick は使った事が有るのですが ImageMagickC言語向けの実装は MagickWand API と MagickCore API という実装になっており「ImageMagickのコマンドを引数に文字列で渡せば出来るのかな?」と軽く考えていた自分の予想を大きく裏切られましたw

しょうが無いので資料は無いかと調べたのですが公式のリファレンスは情報が少なく、一通り実装したようなサンプルは無いのかと探した所やっとこちらのページを見つけました。

[magick-users] Problem Jpeg Images to Gif Animation File
http://studio.imagemagick.org/pipermail/magick-users/2007-January/018986.html

このページを参考にこんな感じに実装しました。 MagickWand のAPIを呼ぶ前に MagickWandGenesis(); をコールし、 MagickWand APIをもう使わなくなった時点で MagickWandTerminus() を呼ぶみたいです。

主な処理としては MagickReadImage() でファイルからデータを読み込み、 MagickSetImageDelay() で表示間隔を設定、 MagickAddImage() でベースとなる画像に読み込んだ画像を追加という流れです。

(2009/10/02 変更)アニメーションの表示される順番が正しくなかったのを修正

	char *flist[] = {"img01.jpg", "img05.jpg", "img04.jpg", "img03.jpg", "img02.jpg", NULL};
	MagickWand *mw = NULL,*rw;
	char **ptr;
	
	MagickWandGenesis();
	mw = NewMagickWand();

	for(ptr = flist; *ptr; ptr++) {
		NSString *inputFile = [NSString stringWithFormat:@"%@/%s" , 
							   [[NSBundle mainBundle] resourcePath],
							   *ptr];
		NSLog(@"入力ファイルパス : %@", inputFile);
		rw = NewMagickWand();
		MagickReadImage(rw, [inputFile UTF8String]);
		MagickSetImageDelay(rw, 60);
		MagickAddImage(mw, rw);
		DestroyMagickWand(rw);
	} 
	// GIF形式で出力
	NSString *outputPath = [NSString stringWithFormat:@"%@/test.gif" , [NSHomeDirectory() stringByAppendingPathComponent:@"Documents"]];  
	NSLog(@"出力ファイルパス : %@", outputPath);
	mw = MagickCoalesceImages(mw);
	MagickBooleanType status = MagickWriteImages(mw, [outputPath UTF8String], MagickTrue);
	NSString *msg;
	if (status == MagickTrue) {
		msg = [NSString stringWithString:@"ファイルの作成に成功しました"];
	} else {
		msg = [NSString stringWithString:@"ファイルの作成に失敗しました"];
	}
	UIAlertView *alert = [[UIAlertView alloc] initWithTitle:nil message:msg
												   delegate:self cancelButtonTitle:nil otherButtonTitles:@"OK", nil];
	[alert show];
	[alert release];
	mw = DestroyMagickWand(mw); 
	MagickWandTerminus(); 

追加するファイルの並びは最初、以下の様に記述していました。

	char *flist[] = {"img01.jpg", "img02.jpg", "img03.jpg", NULL};

しかし、これでは img01.jpg → img03.jpg → img02.jpg という順番でアニメーションしていました。
※コメント欄にてpwwさんに教えて頂きました。ありがとうございます。

色々とテストした結果、以下の様な順番でファイルを追加すると正しい順番で動作する様です。

	char *flist[] = {"img01.jpg", "img05.jpg", "img04.jpg", "img03.jpg", "img02.jpg", NULL};

画像は1枚目が無い場合はそのまま一枚目の位置に追加され、それ以降は2枚目の位置に追加(挿入)される様です。

その他にプロジェクト側にも色々と設定が必要でした。


1. ImageMagick ライブラリの追加

まずは ImageMagick のライブラリをプロジェクトに追加。自分は前回のエントリーで紹介したこちらのページに有る、test project の IM ディレクトリを丸ごとコピーし、使いました。

ImageMagick for iPhone via SnowLeopard
http://www.cloudgoessocial.net/2010/02/10/imagemagick-for-iphone-via-snowleopard/

2. 検索パスの設定

ビルドの設定にて「ヘッダ検索パス」と「ライブラリ検索パス」に "$(SRCROOT)/" を追加し、「再帰的」にチェックを付ける。

3. リンカフラグの設定

「他のリンカフラグ」に以下のものを設定

-lMagickCore -lMagickWand -lbz2 -lz -ljpeg -lpng

サンプルはこちらからDL出来ます。

以前紹介したiTunesを使ったファイル共有機能を実装して有るのでiPhoneで作成したGIFファイルはiTunesを使ってPCに保存出来ます。是非、PCに保存してブラウザにドラックしてちゃんとアニメGIFに成っているか確認してみて下さい。

iTunesを使ったファイル共有機能を使う方法 - 強火で進め
http://d.hatena.ne.jp/nakamura001/20100902/1283442536

なお、こちらのサンプルでは最適化については一切考慮していません。

実際に自分のアプリで使う場合には以下のURLで行っている様な最適化を行うとより良いものとなるでしょう。
※こちらはコマンドライン用のコマンドでアニメGIFを作成する時の最適化方法の解説なので実際のプログラムでは MagickWand の関数に置き換え考える必要が有ります。

Animation Optimization -- IM v6 Examples
http://www.imagemagick.org/Usage/anim_opt/