PHPで画像表示およびconvertコマンドでサムネイル生成
何をするもの?
WEBサイト上でデフォルトサイズの画像ファイルを元に自動的にサムネイル画像を作成し、サムネイル画像作成の手間を省くのと通信容量を必要最小限に抑えることを目的としたものです。
動作条件
サーバ上で以下のものが動作することが前提です。
.htaccess 及び mod_rewrite
ImageMagickのconvertコマンド
ImageMagickはインストールされていてもPHP用ImageMagickモジュール(imagick)まではインストールされていない場合も多いのと、convertコマンドのほうが私にとっては扱いも手っ取り早いのでこちらを使用しています。
設置方法
対象とする画像ファイルのあるディレクトリに
.htaccess
thumbnail.php
をアップロードするだけです。
1 2 3 4 5 |
<IfModule mod_rewrite.c> RewriteEngine On RewriteCond %{REQUEST_URI} ^.*\.(jpe?g|png|gif)$ [NC] RewriteRule ^.*$ thumbnail.php?f=$0&%{QUERY_STRING} [L] </IfModule> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 |
<?php /** * 指定画像のサムネイル画像を自動生成して返します */ // 縮小許容サイズ(これ以外の値を受け取った場合は一番近い値に補正) $resizeList = [50,100,150,200,250,300]; // サムネイルクオリティ(1~100) $quality = 85; // サブディレクトリの画像が指定された場合の動作 // (0:カレントディレクトリと同様にサムネイル作成 1:サムネイルは作らず常にオリジナル画像をパススルー表示) $subDirPathThrough = 1; // 正方形化で余白埋めする場合の余白の色(カラーコード、カラーネームで指定) $fillColor = '#ffffff'; /** * */ // ファイル名パラメータ取得 (通常は .htaccess の設定で自動取得) if(isset($_GET['f'])) { $basename = basename($_GET['f']); $dirname = preg_replace('/\.+\/?/', '', dirname($_GET['f'])); // 本スクリプトが作成したサムネイル保存ディレクトリ名が含まれる場合はnotfoundで終了 if(preg_match('/thumbnail[\w]+/i', $dirname)) { error_exit(404); } $srcFileName = $dirname == '' ? $basename : "{$dirname}/{$basename}"; // サブディレクトリパススルー指定の場合、他のパラメータを削除 if($dirname != '' && $subDirPathThrough) { unset($_GET); } } else { // ファイル名未指定時はnotfoundで終了 error_exit(404); } // 指定されたファイルが存在しない、指定されたファイル名がpng、jpg、gif以外 いずれかの場合は不正なアクセスとみなしnotfoundで終了 if(!file_exists($srcFileName) || !preg_match('/\.(jpe?g|png|gif)$/i', $srcFileName)) { error_exit(404); } if(isset($_GET['t'])) { // tパラメータ取得 $s = array_round($resizeList, (int)$_GET['t']); } else { // tパラメータ省略時は0 $s = 0; } // pパラメータ取得 $progressive = ''; if(isset($_GET['p'])) { // pパラメータが付加されていた場合、jpeg、pngに限りプログレッシブ/インターレースを有効にする $progressive = preg_match('/jpe?g$/i', $srcFileName) ? '-interlace JPEG' : (preg_match('/png$/i', $srcFileName) ? '-interlace PNG' : ''); } // sパラメータ取得 $crop = ''; $fill = ''; $max = 0; if(isset($_GET['s'])) { $crop = 1; } // sfパラメータ取得 elseif(isset($_GET['sf'])) { $fill = 1; } if($s && preg_match('/^\d+$/', $s) || $progressive || $crop || $fill) { $thumbnailDirName = sprintf('thumbnail%s', $s ? $s : '_noresize'); if($dirname) $thumbnailDirName = $dirname. '/'. $thumbnailDirName; $newFileName = "./{$thumbnailDirName}/{$basename}"; if($progressive) $newFileName = preg_replace('/(\.\w+)$/', "_p$1", $newFileName); if($crop) $newFileName = preg_replace('/(\.\w+)$/', "_s$1", $newFileName); if($fill) $newFileName = preg_replace('/(\.\w+)$/', "_f$1", $newFileName); // 対象サイズのサムネイルファイル保存ディレクトリが無い場合はディレクトリ作成 if(!file_exists("{$thumbnailDirName}")) { mkdir("{$thumbnailDirName}", 0755); } if(file_exists($newFileName)) { // 既存のサムネイルよりオリジナルのほうが新しい場合、オリジナルが更新されたとみなし該当サムネイルを削除 if(filemtime($srcFileName) > filemtime($newFileName) || // 既存のサムネイルよりこのスクリプト自身が新しい場合も、設定変更されたとみなし該当サムネイルを削除 filemtime(__FILE__) > filemtime($newFileName) ) { unlink($newFileName); } } // 縮小済みファイルが無い場合のみ作成 if(!file_exists($newFileName)) { if($crop) { list($w, $h) = getimagesize($srcFileName); // 画像の中央を正方形に切り出し if($w != $h) { $min = min([$w, $h]); $crop = "-gravity center -crop {$min}x{$min}+0+0"; } else $crop = ''; } if($fill) { list($w, $h) = getimagesize($srcFileName); // 画像の短辺側を余白色で埋めて正方形化 if($w != $h) { $max = max([$w, $h]); $fillColor = preg_replace('/"*?$/', '"', preg_replace('/^"*?/', '"', $fillColor)); $fill = "-background {$fillColor} -gravity center -extent %dx%d"; if($s) { $fill = sprintf($fill, $s, $s); } else { $fill = sprintf($fill, $max, $max); } } else $fill = ''; } $command = implode(' ', [ 'convert', "\"./{$srcFileName}\"", $crop, $progressive, '-auto-orient -strip', $quality ? sprintf('-quality %d', $quality) : '', $s ? sprintf('-scale %dx%d', $s, $s) : '', $fill, "\"{$newFileName}\"", '']); exec($command, $out, $ret); // convert実行失敗時はInternalServerErrorで終了 if($ret != 0) error_exit(500); chmod($newFileName, fileperms($srcFileName)); } // 生成したファイル名と更新時刻 $imgFilename = $newFileName; $mtime = filemtime($newFileName); } else { // リサイズしない場合はオリジナルのファイル名と更新時刻 $imgFilename = $srcFileName; $mtime = filemtime($srcFileName); } $ifNoneMatch = filter_input(INPUT_SERVER, 'HTTP_IF_NONE_MATCH'); $etag = '"'. md5($mtime. $_SERVER['REQUEST_URI']). '"'; // EtagとリクエストヘッダのIf-None-Matchが異なる場合のみデータを送信、同じ場合はNotModifiedを送ることでブラウザキャッシュを効かせる if($ifNoneMatch != $etag) { header('HTTP/1.1 200 OK'); header(sprintf('Content-Type: image/%s', preg_match('/jpe?g$/i', $srcFileName) ? 'jpeg' : (preg_match('/png$/i', $srcFileName) ? 'png' : 'gif'))); header("Etag: {$etag}"); header(sprintf('Last-Modified: %s', gmdate('D, d M Y H:i:s T', $mtime))); header('Accept-Ranges: bytes'); header(sprintf('Content-Length: %d', filesize($imgFilename))); readfile($imgFilename); } else { header('HTTP/1.1 304 Not Modified'); header("Etag: {$etag}"); } exit; /** * $arr内から$nに一番近い値を返す */ function array_round($arr, $n) { $n = (int)$n; foreach($arr as $v) $arr_[abs($v - $n)] = $v; return $arr_[min(array_keys($arr_))]; } function error_exit($status = 404) { if($status == 404) { header('HTTP/1.1 404 Not Found'); print '<html><body>Not Found</body></html>'; } else { header('HTTP/1.1 500 Internal Server Error'); print '<html><body>Internal Server Error</body></html>'; } exit; } |
使用方法
WEBブラウザから、通常通り画像がアップロードされているディレクトリのURLを指定します。
例 https://akebi.jp/thumbnail_sample/sample.jpg
サムネイルを表示する場合は、?t=サイズ オプションを使います。
例 https://akebi.jp/thumbnail_sample/sample.jpg?t=200
.htaccess に記載した mod_rewrite の記述が、画像ファイル名とそれに付けたクエリパラメータを thumbnail.php に渡す役割をしています。
ここで Internal Server Error が表示された場合は残念ながら convert コマンドが使用できないと思われます。
t= で指定したサイズは、thumbnail.php 内で設定してあるサイズに一番近いものに補正されます。
サイズの種類を増やすこともできますが、あまり多く設定するとその分キャッシュファイル用フォルダも多く作られますので、サーバ容量に余裕がない場合はご注意ください。
その他のクエリパラメータ
s: (squareの略) 正方形でない画像から中央部分を取り出して正方形化
例 https://akebi.jp/thumbnail_sample/sample.jpg?s
sf: (square fillの略) 正方形でない画像の短辺側に余白色を追加して正方形化
例 https://akebi.jp/thumbnail_sample/sample.jpg?sf
p: (progressiveの略) JPEG、PNG画像をプログレッシブ(インターレース)化
例 https://akebi.jp/thumbnail_sample/sample.jpg?p
複数のクエリパラメータを合わせて指定できます。
例 https://akebi.jp/thumbnail_sample/sample.jpg?t=200&sf
処理の概要
↓
有効なクエリパラメータは付いているか
│ ↓
付いている 付いていない
│ ↓
│ オリジナルの画像をそのまま表示
↓
クエリパラメータに対応する画像キャッシュファイルは存在するか
│ ↓
存在しない 存在する
│ ↓
│ 対応する画像キャッシュファイルをそのまま表示
↓
convert コマンドにて対応する画像キャッシュファイルを生成、表示
convert コマンドで作成された画像はサーバ側にキャッシュすることで、以降同じ条件のパラメータで呼ばれた場合には無駄に convert コマンドが実行されないようにしています。
画像ファイル表示処理に関しても、ブラウザから受け取ったリクエストヘッダ内に If-None-Match ヘッダがあるかを確認し、If-None-Match があって画像に対応する ETag との比較結果が一致していれば、HTTP レスポンスステータスとして Not Modified を渡し画像データ自体は送信しないことで、ブラウザ側のキャッシュファイルを有効に働かせ無駄に通信も行なわないようにしています。
使用を中止する場合
画像ディレクトリにアップロードした .htaccess と thumbnail.php 、thumbnail.php によって作成された thumbnail から始まるディレクトリとその中の画像キャッシュファイルを削除して下さい。