[PHP] 画像のサムネイルを作成する方法

2019年8月16日

PHP テクノロジー プログラミング

スマートフォンで撮影した写真をサーバーにアップロードすると、最近の高機能な端末だと数メガバイトの送信が行われます。 写真印刷をする場合などは、画素数が大きければ大きいほど好まれますが、WEBサービスでブラウザ表示するだけであれば、かなりのオーバースペックな素材です。 wordpressの場合に、アイキャッチやブログで使う写真素材をアップロードした場合、元素材の他に、サイズ別に大中小の画像が作成されます。 ある意味この機能は容量の無駄使い機能と思われ、wordpressのディスク圧迫の要因の一つになっています。 サムネイルも含めて、必要なタイミングで、その場面で最適なサイズにリサイズして画像表示する事ができるスニペットを作ってみました。 ただし、この方法は読み込みのたびに画像のサイズ変換をする処理が走るので、ディスクスペースは軽くなりますが、CPUスペックを通常よりも大量に使用するので、サーバー構成を考えて採用してください。

画像リサイズスニペット

<?php if(isset($_REQUEST["image"])){ \image::viewImage($_REQUEST["image"]); } else if(isset($_REQUEST["thumbnail"])){ \image::viewThumbnail($_REQUEST["thumbnail"] , array(200,200) , "cover"); } class image{ // ファイル名(パス)から拡張子を取得 public static function getExtension($imgPath=""){ if(!$imgPath){return null;} $info = pathinfo($imgPath); return self::arrangeExtension($info["extension"]); } public static function getFilename($imgPath=""){ if(!$imgPath){return null;} $info = pathinfo($imgPath); return $info["basename"]; } // 拡張子を統一規格に整える() public static function arrangeExtension($ext=""){ switch(strtolower($ext)){ case "jpg": case "jpeg": return "jpg"; case "gif": return "gif"; case "png": return "png"; case "svg": return "svg"; default: return null; } } // extension->content-type public static function ext2contentType($ext){ switch(strtolower($ext)){ case "jpg": case "jpeg": return "image/jpeg"; case "gif": return "image/gif"; case "png": return "image/png"; case "svg": return "image/svg"; default: return null; } } // 拡張子別画像データの読み込み処理 public static function loadImage($imgPath=""){ if(!$imgPath){return false;} $ext = self::getExtension($imgPath); if(!$ext){return false;} switch($ext){ case "jpg": $image = ImageCreateFromJpeg($imgPath); break; case "gif": $image = ImageCreateFromGif($imgPath); break; case "png": $image = ImageCreateFromPng($imgPath); break; case "svg": default: return false; break; } return $image; } // 画像のサムネイル作成 // mode @ [contain , cover] public static function createThumbnail($currentImgPath="" , $saveImgPath="" , $convertSize=array(128,128) , $mode=""){ if(!$currentImgPath || !is_file($currentImgPath) || !$saveImgPath){return;} $pathinfo = pathinfo($currentImgPath); if(!is_dir($pathinfo["dirname"])){ mkdir($pathinfo["dirname"] , 0777 , true); } $image = self::loadImage($currentImgPath); if(!$image){return 1;} $orientation = 0; echo self::makeImage($saveImgPath , $image , $convertSize , $orientation); exit(); } public static function getThumnailSize($img=null , $convertSize=array()){ if(!$img || !$convertSize){return;} // 画像の実サイズ取得 $x = imagesx($img); $y = imagesy($img); } public static function makeImage( $savePath = "", $image ="", $newSize=array(), $orientation=0){ if(is_file($savePath)){ unlink($savePath); } $ext = self::getExtension($savePath); // $h = $height / ($width / $w); $newImage = imagescale($image, $newSize[0] , $newSize[1]); // orientation switch($orientation){ case 0: // normal case 1: // normal break; case 2: // flip width imageflip($newImage, IMG_FLIP_HORIZONTAL); break; case 3: // rotate 180deg $color = imagecolorallocate($newImage, 0, 0, 0); $newImage = imagerotate($newImage, -180 , $color); break; case 4: // flip height imageflip($newImage, IMG_FLIP_VERTICAL); break; case 5: // flip width + rotate 90deg imageflip($newImage, IMG_FLIP_HORIZONTAL); $color = imagecolorallocate($newImage, 0, 0, 0); $newImage = imagerotate($newImage, -90 , $color); break; case 6: // rotate 90deg $color = imagecolorallocate($newImage, 0, 0, 0); $newImage = imagerotate($newImage, -90 , $color); break; case 7: // flip height + rotate 90deg imageflip($newImage, IMG_FLIP_VERTICAL); $color = imagecolorallocate($newImage, 0, 0, 0); $newImage = imagerotate($newImage, -90 , $color); break; case 8: // rotate 270deg $color = imagecolorallocate($newImage, 0, 0, 0); $newImage = imagerotate($newImage, -270 , $color); break; } // make image switch($ext){ case "jpg": imagejpeg($newImage , $savePath); break; case "png": imagealphablending($newImage, false); imagesavealpha($newImage , true);// 透過処理 imagepng($newImage , $savePath); break; case "gif": imagegif($newImage , $savePath); break; default: return "error"; } // cache削除 imagedestroy($newImage); return "ok"; } public static function viewImage($imagePath=""){ $img = self::loadImage($imagePath); if(!$img){return;} $ext = self::getExtension($imagePath); header('Content-Type: '. self::ext2contentType($ext)); imagejpeg($img); imagedestroy($img); } public static function getImageScale($img=null){ if(!$img){return null;} return array( imagesx($img), imagesy($img) ); } // サイズ変更時のアスペクト比を維持するためにオートサイズ対応(fixed:軸固定※どちらかに負の値がある) public static function getAutoScaleChangeSize_fixed($currentSize=array(),$changeSize=array()){ // 縦横の長さ割合を取得 $rateWidth = $changeSize[0] / $currentSize[0]; $rateHeight = $changeSize[1] / $currentSize[1]; $aspectCurrent = $currentSize[0] / $currentSize[1]; $aspectChange = $changeSize[0] / $changeSize[1]; // ### 辺固定処理 ### // 横サイズのみ固定(縦サイズが負の値) if($changeSize[0] > 0 && $changeSize[1] < 1){ return array( $changeSize[0], (int)($currentSize[1] * $rateWidth) ); } // 縦サイズのみ固定(横サイズが負の値) else if($changeSize[0] < 0 && $changeSize[1] > 1){ return array( (int)($currentSize[0] * $rateHeight), $changeSize[1] ); } } // サイズ変更時のアスペクト比を維持するためにオートサイズ対応(contain:余白ありで全体表示) public static function getAutoScaleChangeSize_contain($currentSize=array(),$changeSize=array()){ if(count($currentSize) !== 2 || count($changeSize) !== 2){return null;} // 縦横の長さ割合を取得 $rateWidth = $changeSize[0] / $currentSize[0]; $rateHeight = $changeSize[1] / $currentSize[1]; $aspectCurrent = $currentSize[0] / $currentSize[1]; $aspectChange = $changeSize[0] / $changeSize[1]; // 横型 if($aspectCurrent > $aspectChange){ return array( $changeSize[0], (int)($currentSize[1] * $rateWidth) ); } // 縦型 else if($aspectCurrent < $aspectChange){ return array( (int)($currentSize[0] * $rateHeight), $changeSize[1] ); } // 変更前と後で同比率 else{ return array( $changeSize[0], $changeSize[1] ); } } // サイズ変更時のアスペクト比を維持するためにオートサイズ対応(cover:トリミングして最大限表示) public static function getAutoScaleChangeSize_cover($currentSize=array(),$changeSize=array()){ if(count($currentSize) !== 2 || count($changeSize) !== 2){return null;} // 縦横の長さ割合を取得 $rateWidth = $changeSize[0] / $currentSize[0]; $rateHeight = $changeSize[1] / $currentSize[1]; $aspectCurrent = $currentSize[0] / $currentSize[1]; $aspectChange = $changeSize[0] / $changeSize[1]; // 横型 if($aspectCurrent < $aspectChange){ return array( $changeSize[0], (int)($currentSize[1] * $rateWidth) ); } // 縦型 else if($aspectCurrent > $aspectChange){ return array( (int)($currentSize[0] * $rateHeight), $changeSize[1] ); } // 変更前と後で同比率 else{ return array( $changeSize[0], $changeSize[1] ); } } // mode @ [contain:余白ありで全体表示 cover:トリミングして最大限表示] public static function viewThumbnail($imagePath="" , $viewSize=array(128,128) , $mode="contain"){ $img = self::loadImage($imagePath); if(!$img){return;} $currentSize = self::getImageScale($img); // 軸固定 if($mode === "fixed"){ $changeSize = self::getAutoScaleChangeSize_fixed($currentSize , $viewSize); $newImage = imagescale($img, $changeSize[0] , $changeSize[1]); } // 全画面表示 else if($mode === "contain"){ $changeSize = self::getAutoScaleChangeSize_contain($currentSize , $viewSize); $newImage = imagescale($img, $changeSize[0] , $changeSize[1]); } // 固定サイズ else if($mode === "cover"){ $changeSize = self::getAutoScaleChangeSize_cover($currentSize , $viewSize); $diffSize = array( ($changeSize[0] - $viewSize[0]) /2 * -1, ($changeSize[1] - $viewSize[1]) /2 * -1 ); $img2 = imagescale($img, $changeSize[0] , $changeSize[1]); $newImage = imagecreatetruecolor($viewSize[0], $viewSize[1]); imagecopyresized($newImage, $img2, $diffSize[0], $diffSize[1] , 0, 0, $changeSize[0], $changeSize[1], $changeSize[0], $changeSize[1]); } // 通常処理 else{ $newImage = imagescale($img, $viewSize[0] , $viewSize[1]); } $ext = self::getExtension($imagePath); header('Content-Type: '. self::ext2contentType($ext)); imagejpeg($newImage); imagedestroy($newImage); } } ※無駄な関数が多いのはご了承ください。 <p>Image-view</p> <img src="image.php?image=sample.jpg" width="300"> <img src="image.php?image=https://upload.wikimedia.org/wikipedia/commons/5/51/Amanohashidate_view_from_Kasamatsu_Park01s3s4410.jpg"width="300"> index.htmlは、かなり簡易に書いていますが、imgタグのsrc属性に、ライブラリを呼び出しているのが分かれば構造は理解できると思います。 imageライブラリで画像のローカルパスやWEB上に存在するURLを登録すれば、そのままimg画像として表示されるというだけなのですが、index.htmlはそのままのサイズで表示するようにしています。

サムネイル表示

<p>Thumbnail-view</p> <img src="image.php?thumbnail=toys-1596716_1280.jpg"> <img src="image.php?thumbnail=https://upload.wikimedia.org/wikipedia/commons/5/51/Amanohashidate_view_from_Kasamatsu_Park01s3s4410.jpg"> 読み込んだ画像のサイズを200x200で表示するようにしています。 \image::viewThumbnail($_REQUEST["thumbnail"] , array(200,200) , "cover"); 上記のコードでサムネイル変換しているのですが、$_REQUEST["thumbnail"]は画像のローカルパスまたはURLを登録し、array(200,200)はサムネイルのサイズ(上限値)を指定しています。 最後の"cover"引数は、cssのobject-fitと同じ仕様にしていて、"cover"の他に"contain"と"fixed"を用意しています。 coverは、指定した画像サイズ(サムネイルサイズ)に当てはまるように画像をクリッピングして表示 containは、指定画像サイズにハマるように画像を縮小して表示 上記2つはアスペクト比をキープして表示されますが、 fixedは、アスペクト比を無視して、指定したサイズの通りにリサイズするようにしています。 ※その際に縦サイズに-1(負の値)を登録すると、横サイズに合わせて自動サイズ設定するようになります。 こうした処理をサービスにセットし、一時的なアクセス過多による負荷を気にする場合は、一度作り出した画像をキャッシュファイルとして出力しておき、一定時間が来たら削除するような処理を手前で行ってもいいかもしれません。 ストレージにも、CPUにも負荷の少なくなるシステム構築は、必須ですからね。

このブログを検索

ごあいさつ

このWebサイトは、独自思考で我が道を行くユゲタの少し尖った思考のTechブログです。 毎日興味がどんどん切り替わるので、テーマはマルチになっています。 もしかしたらアイデアに困っている人の助けになるかもしれません。