幾何学計算アニメーションを作ってみよう

第1回PHPで作る!簡単幾何学アニメ─不思議なプロペラ動画の作り方

先日、「iPhoneでプロペラの回転を撮影したらとんでもないことになってた」という記事を見かけました。⁠ほしのこえ」に出てくる生命体(タルシアンでしたか)のような不気味なフォルムでしたが、動画を見るとなるほどと納得しますね。

ところで、この説明の動画ですが、どうやって作ったらいいのでしょうか。ただの回転なら動画編集ソフトのエフェクトで十分ですが、今回のものは「回転+ちょっとずつ動かしながら切り出し」です。手ごろなツールが見当たりません。

となるとプログラムの出番ですね。さっそくですが、この特集では、このような説明のための動画を作るテクニックをいくつかご紹介していきます。よろしくお付き合いください。

PHPで作る画像表示プログラムの初歩

プログラムで処理した画像を表示するだけなら、Webブラウザを使うと簡単です。今回も使うPHPには、gdライブラリという拡張機能があり、画像の生成や加工が行えます。これはたいていのレンタルサーバでも利用できます。しかし、動画までは対応していません。

そこで、せっかくのWebブラウザなので、クリックするたびに1コマずつ進むようにしてみました。これなら、静止画を出力するだけなので簡単です。まず、長方形が回転するサンプルです。これは、プロペラの1枚にあたります。

画像 画像 画像
リスト1 クリックで長方形が回転するサンプル(rotate_1.php)
<?php

$frame = @$_GET["frame"] + 0;
if (($frame < 0)||($frame > 100))
	$frame = 0;
switch (@$_GET["mode"]) {
	case	"image":
		break;
	default:
		$nextframe = $frame + 1;
		print <<<EOO
<HTML><HEAD><TITLE>rotate.php</TITLE></HEAD><BODY>
<H1>rotate.php</H1>

<A href="?frame={$nextframe}"><IMG src="?frame={$frame}&mode=image"></A>

<HR>
</BODY></HTML>
EOO;
		die();
}

$width = 400;
$height = 400;
$g = imagecreate($width, $height);
$c0 = imagecolorresolve($g, 255, 255, 255);
$c1 = imagecolorresolve($g, 0, 0, 0);
imagefilledrectangle($g, 0, 0, $width, $height, $c0);

function	drawblade($g, $color, $a)
{
	global	$width, $height;
	
	$cx = $width / 2;
	$cy = $height / 2;
	$list = array(
		array(-20, -180), 
		array(20, -180), 
		array(20, 0), 
		array(-20, 0)
	);
	$points = array();
	$sin = sin($a * 3.14159 / 180);
	$cos = cos($a * 3.14159 / 180);
	foreach ($list as $p) {
		list($x, $y) = $p;
		$points[] = round($cx + $x * $cos - $y * $sin);
		$points[] = round($cy + $x * $sin + $y * $cos);
	}
	imagefilledpolygon($g, $points, count($points) / 2, $color);
}


drawblade($g, $c1, $frame * 10);
header("Content-Type: image/png");
imagepng($g);
imagedestroy($g);
?>

クリックを受け付けるのはHTML部分になりますので、ここからIMGタグで画像を読み込んでいます。リクエストが分割されるので面倒な面もありますが、特別複雑なところはありません(座標の計算方法は次回で取り上げますので、ここではブラックボックスだと思ってください⁠⁠。その上、wgetなどでまとめて画像を得ることもできるため、応用範囲が広くなりました。

画像を扱うプログラムでやっかいなのは、ユーザインターフェースです。その場でパラメータを入力するとすぐさま画像に反映されるようなプログラムは、作るのにとても手間がかかります。今回はWebベースにすることができましたので、この辺はうまく解決することができました。

プログラムですが、白の背景に、黒の長方形を描画しています。長方形を120°ずつ3回回転させると、次のようになります。これでプロペラができました。角度を変えれば、何枚羽根のプロペラでも作れますね。

画像 画像 画像
リスト2 プロペラの回転サンプル(rotate_2.php)
<?php

$frame = @$_GET["frame"] + 0;
if (($frame < 0)||($frame > 100))
	$frame = 0;
switch (@$_GET["mode"]) {
	case	"image":
		break;
	default:
		$nextframe = $frame + 1;
		print <<<EOO
<HTML><HEAD><TITLE>rotate.php</TITLE></HEAD><BODY>
<H1>rotate.php</H1>

<A href="?frame={$nextframe}"><IMG src="?frame={$frame}&mode=image"></A>

<HR>
</BODY></HTML>
EOO;
		die();
}

$width = 400;
$height = 400;
$g = imagecreate($width, $height);
$c0 = imagecolorresolve($g, 255, 255, 255);
$c1 = imagecolorresolve($g, 0, 0, 0);
imagefilledrectangle($g, 0, 0, $width, $height, $c0);

function	drawblade($g, $color, $a)
{
	global	$width, $height;
	
	$cx = $width / 2;
	$cy = $height / 2;
	$list = array(
		array(-20, -180), 
		array(20, -180), 
		array(20, 0), 
		array(-20, 0)
	);
	$points = array();
	$sin = sin($a * 3.14159 / 180);
	$cos = cos($a * 3.14159 / 180);
	foreach ($list as $p) {
		list($x, $y) = $p;
		$points[] = round($cx + $x * $cos - $y * $sin);
		$points[] = round($cy + $x * $sin + $y * $cos);
	}
	imagefilledpolygon($g, $points, count($points) / 2, $color);
}


drawblade($g, $c1, $frame * 10);
drawblade($g, $c1, $frame * 10 + 120);
drawblade($g, $c1, $frame * 10 + 240);

header("Content-Type: image/png");
imagepng($g);
imagedestroy($g);
?>

ちなみに、コマ送りの画像ができたら、ImageMagickなどを使うと動画ファイルに変換することができます。たとえば、

convert -delay 20 *.png test.mpeg

とすれば、200ミリ秒間隔のコマ送り動画ができます。数字は10ミリ秒単位で、出力を.gifにするとアニメーションGIFも作れます。またWindows専用ですが、JPG2AVIのようなフリーソフトも利用できると思います。

プロペラ回転+切り出しアニメの作成

では、いよいよ本命の、プロペラ回転+切り出しです。今回は時間がかかってもいいので、簡単な方法を選びました。

画像 画像 画像
リスト3 プロペラが回転しながら切り出されるサンプル(rotate_3.php)
<?php

$width = 400;
$height = 400;

$frame = @$_GET["frame"] + 0;
if (($frame < 0)||($frame > 100))
	$frame = 0;
switch (@$_GET["mode"]) {
	case	"image":
		break;
	default:
		$nextframe = $frame + 1;
		print <<<EOO
<HTML><HEAD><TITLE>rotate.php</TITLE></HEAD><BODY>
<H1>rotate.php</H1>

<A href="?frame={$nextframe}"><IMG src="?frame={$frame}&mode=image" width={$width} height={$height}></A>

<HR>
</BODY></HTML>
EOO;
		die();
}

$g = imagecreatetruecolor($width, $height);
$c0 = imagecolorresolve($g, 255, 255, 255);
$c1 = imagecolorresolve($g, 0, 0, 0);
imagefilledrectangle($g, 0, 0, $width, $height, $c0);

function	drawblade($g, $color, $a)
{
	global	$width, $height;
	
	$cx = $width / 2;
	$cy = $height / 2;
	$list = array(
		array(-20, -180), 
		array(20, -180), 
		array(20, 0), 
		array(-20, 0)
	);
	$points = array();
	$sin = sin($a * 3.14159 / 180);
	$cos = cos($a * 3.14159 / 180);
	foreach ($list as $p) {
		list($x, $y) = $p;
		$points[] = round($cx + $x * $cos - $y * $sin);
		$points[] = round($cy + $x * $sin + $y * $cos);
	}
	imagefilledpolygon($g, $points, count($points) / 2, $color);
}

$gb = imagecreatetruecolor($width, $height);
$cb0 = imagecolorresolve($gb, 255, 255, 255);
$cb1 = imagecolorresolve($gb, 0, 0, 0);
for ($pos=0; $pos<=$frame; $pos++) {
	imagefilledrectangle($gb, 0, 0, $width, $height, $cb0);
	
	$a = $pos * 10;
	drawblade($gb, $cb1, $a);
	drawblade($gb, $cb1, $a + 120);
	drawblade($gb, $cb1, $a + 240);
	
	$y = $pos * 5;
	imagecopy($g, $gb, 0, $y, 0, $y, $width, $height - $y);
	$y += 5;
	imageline($g, 0, $y, $width, $y, $c1);
}
imagedestroy($gb);

header("Content-Type: image/png");
imagepng($g);
imagedestroy($g);
?>

まず、さきほどのプロペラの画像の1コマ目を用意します。この画像は、縦横400ドットのビットマップです。同じ大きさのビットマップを用意して、1コマ目全体をそちらにコピーします。

続いて2コマ目です。2コマ目は上端の20ドットは1コマ目のものを残し、そこから下はプロペラ画像の2コマ目をコピーします。つまり、プロペラ画像の上端の20ドットはコピーせず、1コマ目のものを残します。

3コマ目は、上端の40ドットはそのままにして、そこから下にプロペラ画像の3コマ目をコピーします。

Webブラウザからアクセスした場合、後半のコマほど生成に時間がかかってしまいますが、とりあえず画像はできました。あとは全部のファイルを出力して、先に紹介したImageMagickなどで動画に変換すれば、解説動画として動画サイトなどにアップロードできるはずです。

rotate_3.phpの実行結果(ここではgifアニメにしてあります)
rotate3.phpの実行結果

今回は、あらかじめプログラムした動きを、動画に出力しました。実際には、マウス操作のような動きを使いたいこともあるでしょう。そこで次回は、JavaScriptを使い、ブラウザ側の動きを読みとってサーバ側で動画にする、というのをやってみようと思います。ご期待ください。

おすすめ記事

記事・ニュース一覧