【PHP実践|実務向け】GDライブラリでPNG/GIFの透明度を維持したまま画像リサイズを行う実装テクニック

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等)を使用し、ディレクトリトラバーサル攻撃への対策を怠らないようにしましょう。

    タイトルとURLをコピーしました