2011年2月25日金曜日

GDを使った画像のリサイズ

リサイズに限らないけど、こういうありがち処理って「画像操作用クラス」とか作ってること多いから、いざ直接GDの関数使うような機会があっても、それはもうすっかりみごとに忘れてる。
んで毎回同じような事調べるわけよ、GIF画像の透過部分が黒くなったぞー!とか。

ということでまたGDを使って画像を縮小する機会があったので、忘れないうちにメモしとく。

大前提

リサイズする時の処理の流れとしては、
  1. 元画像をGDで開く
  2. 新規TrueColorイメージをリサイズ後のサイズで作る
  3. imagecopyresampled()を使って1をコピー元、2をコピー先で再サンプリング
  4. 2を適切な名前で保存、もしくは表示する
  5. 1, 2のイメージリソースを開放
こんな感じ。

imagecopyresized()じゃないのは、imagecopyresampled()を使った方が品質が良いから。
マニュアルのimagecopyresizedとこに書いてある。

PHPで書くとこんな感じか。
/* 1. 元画像を開く */
$original = imagecreatefromgif('./sample.gif');
$x = imagesx($original);
$y = imagesy($original);

/* 2. 新規TrueColorイメージをリサイズ後のサイズで作る */
$resize = imagecreatetruecolor($x/2, $y/2);

/* 3. 1をコピー元、2をコピー先で再サンプリング */
imagecopyresampled($resize, $original, 0, 0, 0, 0, $x/2, $y/2, $x, $y);

/* 4. 2を適切な名前で保存、もしくは表示する */
imagegif($resize, './resized_sample.gif');

/* 5. イメージリソースを開放 */
imagedestroy($original);
imagedestroy($resize);
あ、imagecreatefromgif()imagegif()の部分は画像形式ごとに、imagecreatefromjpeg(), imagepng()とか適切に変えないといけない。
なんともめんどくさーだけど、実際にはgetimagesize()で画像形式取得するとかして分岐する事になるんかな。まぁ、一緒に後々なにかと使う縦横サイズも分かったりして、便利だったりもするけど。
って、問題はそこじゃない。

問題点

リサイズ時の最大の問題点は、「なんで透過部分が黒くなるんだよー(泣)」ってなる事だ。
透過部分なので、JPEGは透過しないから問題ない。
GIFとPNGね。

理由は、imagecreatetruecolor()がそもそも黒いTrueColorイメージを作るから。

以下、解決方法。
ちなみに、指定した1色を透明として扱うGIFと、アルファチャネルを持つPNGでは、解決方法が異なる。

透過GIFの場合

imagecopyresampled()が元画像から透過色の情報までコピーしてくれる訳じゃないからGIFとして保存(ないし出力)する前にimagecolortransparent()で透過色を指定しないとダメ。
そしてimagecreatetruecolor()が黒に塗りつぶされた新規イメージを作る影響をモロに受けるので、一番最初にその黒を透過色で塗りつぶしてあげないといけない。
(じゃないと透過して黒い背景が見える→透過部分が黒く塗りつぶされた様に見える)

という事でこんな感じになる。
$original = imagecreatefromgif('./sample.gif');
$x = imagesx($original);
$y = imagesy($original);

$resize = imagecreatetruecolor($x/2, $y/2);
$alpha = imagecolortransparent($original);  // 元画像から透過色を取得する
imagefill($resize, 0, 0, $alpha);       // その色でキャンバスを塗りつぶす
imagecolortransparent($resize, $alpha); // 塗りつぶした色を透過色として指定する

imagecopyresampled($resize, $original, 0, 0, 0, 0, 80, 60, 160, 120);

imagegif($resize, 'resize_sample.gif');

imagedestroy($original);
imagedestroy($resize);
別に透過色は何色でもいいんだけど、指定した色が迂闊に画像内で使われてたりすると使われてる部分も透過しちゃうから、元画像の透過色を使うのが無難だと思う。

透過PNGの場合

透明部分は黒いわ半透明は不透明になるわで、PNGはGIFよりさらにヒドイ有様に・・・。
これは個人的な印象だけど、GDの内部形式ってGIFっぽい感じなんだと思うんだ。
実際PNGのサポートはgd-1.6以降からだし、何も指定してあげないとGDはアルファチャネルをちゃんと扱ってくれない。
ということで、ちゃんとアルファチャネル情報を保存するようにimagesavealpha()でフラグをセットしてあげると半透明は解決する。

imagesavealpha()はマニュアルに、
imagesavealpha() は PNG 画像を保存する際に(単一色の透過設定ではない)完全な アルファチャネル情報を保存するフラグを設定します。 これを使用するためには、アルファブレンディングを解除する必要があります (imagealphablending($im, false))。
ってあるのでブレンドモードをオフにするんだけど、これで透明部分が黒くなるのも解決する。
ということでこんな感じになる。
$original = imagecreatefrompng('./sample.png');
$x = imagesx($original);
$y = imagesy($original);

$resize = imagecreatetruecolor($x/2, $y/2);
imagealphablending($resize, false);  // アルファブレンディングをoffにする
imagesavealpha($resize, true);       // 完全なアルファチャネル情報を保存するフラグをonにする
imagecopyresampled($resize, $original, 0, 0, 0, 0, $x/2, $y/2, $x, $y);

imagepng($resize, './resized_sample.png');
imagedestroy($original);
imagedestroy($resize);