ECナビの子会社unigameでソーシャルゲームの開発をしている谷西(@tnnsst35)といいます。
新卒入社3年目のまだまだひよっこですが、お付き合いいただければと思います。

今回僕がお話しするのは「画像合成」についてです。

* 画像合成って?

ソーシャルゲームでは、キャラクターの表情、洋服、持ち物など、組み合わせ方によっては何パターンも存在するような画像を扱う必要があります。
そんな数え切れないパターンの画像をひとつひとつデザイナーに作ってもらうのは気の遠くなる話です。
そこで登場するのが、「画像合成」です。

画像合成は文字どおり、画像Aと画像Bを合成して、新しく画像Cを生み出すということです。

unigameで開発しているほぼ全てのソーシャルゲームで画像合成は登場しています。
unigameではPHPのGDライブラリを用いて画像合成をしています。

* GDで画像合成

例として
キャラクター(img/chara.gif) に 青い洋服(img/wears/blue.gif) を着せた画像を img/compose/blue_chara.gif として保存する方法をGDでやってみましょう。

<?php
    $baseImg = imageCreateFromGif('img/chara.gif');       //img/chara.gifを読み込む
    $wearImg = imageCreateFromGif('img/wears/blue.gif');  //img/wears/blue.gifを読み込む

    $size = getImageSize('img/wears/blue.gif');           //img/wears/blue.gifのサイズを取得する
    $width  = $size[0];
    $height = $size[1];

    imageCopy($baseImg, $wearImg, 16, 32, 0, 0, $width, $height);  //画像を合成する

    imageDestroy($wearImg);                                        //メモリを開放する

    $newFile = 'img/compose/blue_chara.gif';
    imageGif($baseImg, $newFile);             //img/compose/blue_chara.gifとして書き出す
    chmod($newFile, 0775);                    //権限を775に変更する

    imageDestroy($baseImg);                   //メモリを開放する
?>
 
どうでしょうか?

ソースコードをみてみると、実に簡単でわかりやすい処理なのかがわかると思います。
imageCreateFromGifで画像を読み込み、imageCopyに読み込んだ画像と合成する座標を引数で渡し、imageGifで書き出す。
たったこれだけで出来てしまいます。
また、複数の画像を合成したい場合は、imageCopyを繰り返せば実現できます。

実際の運用では一度保存した画像は再度合成しないように file_exists でファイルがなければ合成するようにします。


<?php
    $newFile = 'img/compose/blue_chara.gif';
    if (!file_exists($newFile)) {
        //画像合成の処理
    }
?>

* Imagickで画像合成

unigameではGDを用いていますが、他にもいくつか方法はあるようです。
今回は PECL::Imagick を用いた方法も試してみました。


<?PHP
    $baseImg = new Imagick('img/chara.gif');         //img/chara.gifを読み込む
    $wearImg = new Imagick('img/wears/blue.gif');    //img/wears/blue.gifを読み込む

    $baseImg->compositeImage($wearImg, $wearImg->getImageCompose(), 16, 32);  //画像を合成する

    $wearImg->destroy();                      //メモリを開放する

    $newFile = 'img/compose/blue_chara.gif';
    $baseImg->writeImage($newFile);           //img/compose/blue_chara.gifとして書き出す
    chmod($newFile, 0775);                    //権限を775に変更する

    $wearImg->destroy();                      //メモリを開放する
?>

コードをみるとわかりますが、やってることはGDとほとんど変わりません。
一見するとどちらでやっても同じ結果を生み出すので、どちらも採用しても良い感じがしますが、
実は速度に差があるのです。

* GDとImagickの比較

画像合成する処理を 10000回繰り返し行い、かかった時間を測定するテストを行いました。


<?PHP
    //GDのテスト
    $startTime = time();

    for ($i = 0;$i < 10000;$i++) {
        $baseImg = imageCreateFromGif('img/chara.gif');       //img/chara.gifを読み込む
        $wearImg = imageCreateFromGif('img/wears/blue.gif');  //img/wears/blue.gifを読み込む

        $size = getImageSize('img/wears/blue.gif');           //img/wears/blue.gifのサイズを取得する
        $width  = $size[0];
        $height = $size[1];

        imageCopy($baseImg, $wearImg, 16, 32, 0, 0, $width, $height);  //画像を合成する

        imageDestroy($wearImg);     //メモリを開放する
        imageDestroy($baseImg);     //メモリを開放する
    }

    $endTime = time();

    echo 'GD : ' . ($endTime - $startTime);

    //imagickのテスト

    $startTime = time();

    for ($i = 0;$i < 10000;$i++) {
        $baseImg = new Imagick('img/chara.gif');         //img/chara.gifを読み込む
        $wearImg = new Imagick('img/wears/blue.gif');    //img/wears/blue.gifを読み込む

        $baseImg->compositeImage($wearImg, $wearImg->getImageCompose(), 16, 32);  //画像を合成する

        $wearImg->destroy();                      //メモリを開放する
        $wearImg->destroy();                      //メモリを開放する
    }

    $endTime = time();

    echo ', imagick : ' . ($endTime - $startTime);
?>

テスト1回目 GD : 4, imagick : 10 
テスト2回目 GD : 4, imagick : 11 
テスト3回目 GD : 5, imagick : 10
テスト4回目 GD : 4, imagick : 9
テスト5回目 GD : 4, imagick : 11

テスト100回してみましたが、GDの方がimagickに比べて2倍の速度があることがわかりました。
画質に関してはフィーチャーフォンでみる分には、ほとんど違いがありませんでした。

* まとめ

ソーシャルゲームでは、どれだけ早くレスポンスを返せるのかということが、ストレスなくゲームを楽しんでもらうための重要な指標のひとつになります。
PHP×ソーシャルゲーム×画像合成にはGDをオススメしたいと思います。

※ 今回のベンチマークで使用した環境はSakura InternetのVPS512プランです。
  環境によっては必ずしも同じ結果にはならない事もありますので、ご了承ください。