pen00log

しがないwebエンジニアです

【PHP】画像のサムネイル化について

PHPでの画像のサムネイル作成の手順について、 一見何をしているのかさっぱり分からなかったので 自分なりに噛み砕いてみましたメモです。

最初「サムネイル表示されるようにしたいな」と思った時は なんとなく、元画像と幅と高さを引き数に入れておけば その通りに画像を伸縮してくれる関数とか用意されてるんじゃないの~ とか勝手に思っていたんですけどそんなに甘くありませんでした・・笑

コードを載せる前に、サムネイルができるまでの工程を記しておきます。

①元画像をサーバー上にアップする
②拡張子の確認
③元画像からサムネイルの幅と高さを算出
④算出した幅、高さの真っ黒な画像をサーバー上に作成
⑤真っ黒な画像に、元画像を貼り付ける


プリンターで写真を現像するとき、欲しい大きさの用紙を用意して(この場合は真っ白な紙だけど) 画像を印刷しますよね?イメージはそれと同じです!

では全体のコードを載せます。前提として、

<form method="POST" action="<?php print($_SERVER['PHP_SELF']) ?>"enctype="multipart/form-data">
<input type="file" name="upfile">
<input type="submit" name="btn" value="送信">
</form>

こんなかんじのフォームとファイル名(upfile)で画像ファイルが送られているとします。

//拡張子を見る
<?php
$tmp = pathinfo($_FILES["upfile"]["name"]);
$extension = $tmp["extension"];
if ($extension === "jpg" || $extension === "jpeg" || $extension === "JPG" || $extension === "JPEG") {
         $extension = "jpeg";
    } elseif ($extension === "png" || $extension === "PNG") {
         $extension = "png";
    } elseif ($extension === "gif" || $extension === "GIF") {
         $extension = "gif";
    } else {
         echo "非対応ファイルです.<br/>";
         exit(1);//プログラムを終了する
    }

//画像データのサムネイル作成
if ($extension === "jpeg" || $extension === "png" || $extension === "gif") {
        //$_FILES['upfile']['mime']の値はブラウザ側で偽装可能なので
        // MIMEタイプを自前でチェックする
        //例:jpegファイルの場合
        //getimagesize():$info[0]=画像の幅、$info[0]=画像の幅、$info[2]=2(IMAGETYPE_JPEG),$info['mine']=image/jpeg
        if (!$info = @getimagesize($_FILES['upfile']['tmp_name'])) { //falseのときはエラーメッセージを出したいので、@でエラー制御
                throw new RuntimeException('有効な画像ファイルを指定してください', 400);
            }
        $array = array(IMAGETYPE_GIF, IMAGETYPE_JPEG, IMAGETYPE_PNG);
        if (!in_array($info[2], $array, true)) {
                throw new RuntimeException('未対応の画像形式です', 400);
            }
            
        $create = str_replace('/', 'createfrom', $info['mime']); //image/jpeg→imagecreatefromjpeg
        $output = str_replace('/', '', $info['mime']); //image/jpeg→imagejpeg
         
        //サムネイル画像の幅、高さを算出
        //長いほうの辺を120ピクセルとし、もう一方の辺をそれにあわせて縦横比が変わらないように伸縮
        if ($info[0] >= $info[1]) {
        $dst_w = 120;
        //ceil():数値を整数値へ切り上げて返す
        $dst_h = ceil(120 * $info[1] / max($info[0], 1)); //画像サイズの変更
        } else {
            $dst_w = ceil(120 * $info[0] / max($info[1], 1)); 
            $dst_h = 120;
        }
    //$create = imagecreatefromjpeg('ファイル名'):'ファイル名'のデータをもとにサーバー上に画像を作成
    if (!$src = @$create($_FILES['upfile']['tmp_name'])) {
            throw new RuntimeException('画像リソースの生成に失敗しました', 500);
        }
    //imagecreatetruecolor():指定された幅と高さの真っ黒の画像を作成
    $dst = imagecreatetruecolor($dst_w, $dst_h);
    //真っ黒の画像($dst)に乗せたい画像($src)を載せる作業
    imagecopyresampled($dst, $src, 0, 0, 0, 0, $dst_w, $dst_h, $info[0], $info[1]);
    //image/jpeg():画像の出力
    $output($dst); 
    //念のため?メモリを開放しておく
    imagedestroy($src);
    imagedestroy($dst);
}
>


2~14行目
pathinfo()を使って拡張子を調べて、表記を揃えています。
今回のようにサムネイルを出力するだけの場合、別に揃える必要は無いのですが、 データベースをに一旦保存して表示させるためにこの手順を踏んでいます。

18~31行目
必要な情報を準備しています。
元となるjpeg画像からサーバー上にデータをを生成するimagecreatefromjpeg()や、 画像を出力するimagejpeg()はそれぞれ拡張子ごとに 関数名が変わるのですが、if文で分岐すると大変なのでinfo()の返り値である$info['mime']をうまく使って 関数名をそれぞれ$create、$outputに保存しています。

33~42行目
サムネイル後の画像の大きさを決定しています。
このコードでは、参考サイトのコードを拝借して 長いほうの辺を120pxとし縦横比を維持したまま伸縮するような計算にしていますが、 正直max()を使う必要があるのかどうかはよく分かりませんでした・・。

43~52行目
具体的にサムネイル化を行う処理をしています。
imagecreatefromjpeg()(ここでは$create())で元画像をサーバー上に用意し、 imagecreatetruecolor()でサムネイルの大きさの真っ黒の画像を作って imagecopyresampled()で元画像を黒い画像に貼り付けています。
たくさん引き数がありますが、この関数は 元の画像の指定した範囲を、貼り付けたい画像の指定した範囲に貼り付けてくれる関数です。
(意味がわからなくても、私の日本語力の問題なのでスルーしてください笑
ちなみにdstはdestination(行き先)、srcはsource(情報源)の略です。

imagecopyresampled ( $dst, $src, $dst_x, $dst_y, $src_x, $src_y, $dst_w,  $dst_h , $src_w, $src_h )

$dst:コピー先の画像リンクリソース
$src:コピー元の画像リンクリソース
$dst_x:コピー先の x 座標
$dst_y:コピー先の y 座標
$src_x:コピー元の x 座標
$src_y:コピー元の y 座標
$dst_w:コピー先の幅
$dst_h:コピー先の高さ
$src_w:コピー元の幅
$src_h:コピー元の高さ

言葉じゃうまく伝わらないので歌に・・、ではなく図にしてみました。

f:id:pen_00:20181029225144p:plain

座標は左上を原点に取り、srcの破線内をdstの破線内に貼り付けるということをしてくれます。

なのでサムネイルの作成でいうと、 「それぞれ画像の左上端である(0, 0)を原点に画像全体を選択するように指定して、貼り付けを行う」 という作業になります。

f:id:pen_00:20181029224615p:plain

余談ですが、「なぜ座標が左下原点じゃなくて左上原点なのか?」 という点についておそらくコードや横書きのテキストを読み込むのと同じように 上から下に一行ずつ、左から右へ1ピクセルずつ読み込んでいるんだと思います。(たぶん)

かなりだらだらと書きましたが、誤り等ありましたらご指摘いただけるとうれしいです。
ここまで読んでくださりありがとうございました!

参考URL

PHP+MySQLで簡易画像アップローダ - Qiita

PHP: imagecopyresampled - Manual

それでも僕が、エラー制御(抑制)演算子"@"を使う理由 - noopな日々