1. 導入
Webアプリケーション開発において、ユーザーがアップロードした画像をリサイズして保存する機能は非常に一般的です。しかし、PHP標準の画像処理ライブラリである「GD」を使用してリサイズを行う際、単に imagecopyresampled 関数を呼ぶだけでは、PNGやGIFが持つ「透明部分」が黒く塗りつぶされてしまうという課題があります。本記事では、透過情報を適切に保持し、実務レベルで安全に画像リサイズを行うための実装パターンを解説します。
2. 基礎知識
GDライブラリはPHPで動的に画像を生成・編集するための拡張モジュールです。特に透過PNGや透過GIFを扱う際、画像リソースを作成した直後は背景が黒色(または透明色ではない色)として初期化されることが多いため、適切なアルファチャンネルの設定やカラーインデックスのコピーが必要です。また、セキュリティの観点から、拡張子だけで判断せず、finfo クラスを用いて「MIMEタイプ」を正しく判定することが必須となります。
3. 実装/解決策
実装のポイントは以下の3点です。
1. finfo クラスによるMIMEタイプ判定:ファイルの拡張子を信用せず、バイナリ情報から正当な画像形式を特定します。
2. 透過処理の分岐:GIFの場合はカラーインデックスを、PNGの場合はアルファブレンディングを制御して透明度を維持します。
3. リソースの解放:imagedestroy を使用し、メモリリークを防ぎます。
4. サンプルプログラム
以下の関数は、指定した最大幅・高さに収まるようアスペクト比を維持してリサイズを行い、透過情報を保持したまま画像を保存する実用コードです。
/
function uploadImage($tmpName, $dir, $maxWidth, $maxHeight) {
// ファイルのMIMEタイプを正確に取得
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime = $finfo->file($tmpName);
// MIMEタイプに応じた画像生成
if ($mime == ‘image/jpeg’) {
$ext = ‘.jpg’;
$image1 = imagecreatefromjpeg($tmpName);
} elseif ($mime == ‘image/png’) {
$ext = ‘.png’;
$image1 = imagecreatefrompng($tmpName);
} elseif ($mime == ‘image/gif’) {
$ext = ‘.gif’;
$image1 = imagecreatefromgif($tmpName);
} else {
return false; // 非対応フォーマット
}
// 元画像サイズ取得
list($width1, $height1) = getimagesize($tmpName);
$scale = min($maxWidth / $width1, $maxHeight / $height1, 1.0);
$width2 = (int)($width1 $scale);
$height2 = (int)($height1 $scale);
// リサイズ先画像リソース作成
$image2 = imagecreatetruecolor($width2, $height2);
// 透過処理の維持(ここが重要)
if ($ext == ‘.gif’) {
$transparent1 = imagecolortransparent($image1);
if ($transparent1 >= 0) {
$index = imagecolorsforindex($image1, $transparent1);
$transparent2 = imagecolorallocate($image2, $index[‘red’], $index[‘green’], $index[‘blue’]);
imagefill($image2, 0, 0, $transparent2);
imagecolortransparent($image2, $transparent2);
}
} elseif ($ext == ‘.png’) {
imagealphablending($image2, false); // アルファブレンディング無効化
$transparent = imagecolorallocatealpha($image2, 0, 0, 0, 127);
imagefill($image2, 0, 0, $transparent);
imagesavealpha($image2, true); // アルファチャンネル保存を有効化
}
// リサイズ実行
imagecopyresampled($image2, $image1, 0, 0, 0, 0, $width2, $height2, $width1, $height1);
// 保存ディレクトリ作成
if (!file_exists($dir)) mkdir($dir, 0777, true);
// 一意なファイル名生成
$saveTo = $dir . ‘/’ . bin2hex(random_bytes(16)) . $ext;
// 出力
if ($ext == ‘.jpg’) imagejpeg($image2, $saveTo, 80);
elseif ($ext == ‘.png’) imagepng($image2, $saveTo);
elseif ($ext == ‘.gif’) imagegif($image2, $saveTo);
// メモリ解放
imagedestroy($image1);
imagedestroy($image2);
return $saveTo;
}
?>
5. 応用・注意点
メモリ管理の注意点: 高解像度の画像(例:数千ピクセル以上の写真)を一度にリサイズしようとすると、PHPの memory_limit に達し、Fatal Error になる場合があります。本番環境では、画像処理を行う前に getimagesize() でピクセル数を確認し、極端に大きい画像は処理を拒否するか、Imagick(ImageMagick)などの外部ライブラリの利用を検討してください。また、ファイル名には安全なランダム文字列(random_bytes等)を使用し、ディレクトリトラバーサル攻撃への対策を怠らないようにしましょう。