PHP レッスン — レッスン 4 — 画像の処理、GD2 ライブラリ
前のレッスンで DB へのクエリの書き方を学んだので、今回はクエリの書き方にはあまり注目せず、単にクエリを書く練習をします。また、クエリの記述を PHP の他の機能の学習と組み合わせていきます。まずは画像の処理から始めましょう。前のレッスンの 1 つですでにファイルをアップロードし、アップロードされたファイル用の Files テーブルもあります。同じテーブルに画像をアップロードしましょう。ただしまず、素材作成フォームに写真アップロード用のフィールドを追加する必要があります。
$content .= '<label for="bodytext">メッセージ:</label><br />';
$content .= '<textarea name="bodytext" id="bodytext"></textarea><br />';
$content .= '<label for="bodytext">ファイルを添付:</label><br /><input type="file" name="filename"><br />'; // ファイルアップロードフィールド
$content .= '<label for="bodytext">画像を添付:</label><br /><input type="file" name="image">'; // ファイルアップロードフィールド
次にファイルの処理を書く必要がありますが、まず Messages テーブルにもう 1 つ列を追加します。そこに画像の fid を記録し、画像のデータは他のファイルと同様に Files テーブルに記録します。列は image_fid と名付け、その特性は INT(11)、デフォルト NULL です。では、フォームの処理を write() メソッドに挿入しましょう:
if($_FILES["image"]["size"] > 1024*3*1024){
echo ("ファイルサイズが 3 メガバイトを超えています");
exit;
}
if(is_uploaded_file($_FILES["image"]["tmp_name"])){ // ファイルがアップロードされたか確認する
// ファイルが正常にアップロードされたら、
// 一時ディレクトリから最終ディレクトリへ移動する
move_uploaded_file($_FILES["image"]["tmp_name"], "./files/".$_FILES["filename"]["name"]);
} else {
echo("ファイルアップロードのエラー");
}
$sql = 'INSERT INTO Files (filename, filepath, filemime, filesize, timestamp)
VALUES ("'. $_FILES['image']['name'] . '",
"files/' . $_FILES['image']['name'] . '",
"'. $_FILES['image']['type'] .'",
'. $_FILES['image']['size'] .',
'. time() . ')';
mysql_query($sql);
この処理は通常のファイルのアップロードと何ら変わりませんが、次のレッスンの 1 つで、画像を装って例えば pdf ドキュメントがアップロードされないよう、どのタイプのファイルがアップロードされるかをチェックする方法を扱います。
処理を書いたので、これで 2 つのファイルがアップロードされ、そのうち 1 つが画像です。print_r() で出力したところ、$_POST と $_FILES の配列は次のようになりました:
Array (
[title] => test
[bodytext] => asdfasf
)
Array (
[filename] => Array (
[name] => ip.txt
[type] => text/plain
[tmp_name] => Y:\tmp\php2C5.tmp
[error] => 0 [size] => 13
)
[image] => Array (
[name] => Coat_of_arms_of_Vladimiri_Oblast.png
[type] => image/png
[tmp_name] => Y:\tmp\php2C6.tmp
[error] => 0 [size] => 393743
)
)
メッセージ挿入のクエリも変更する必要があります:
$sql = 'INSERT INTO Messages (title, bodytext, created, fid, image_fid) VALUES ("'. $p["title"] . '", "' . $p["bodytext"] . '", ' . time() . ',LAST_INSERT_ID()-1, LAST_INSERT_ID())';
通常のファイルと画像の fid を判定するために、私は LAST_INSERT_ID() 関数を使います。通常のファイルが画像より前に挿入されるので、最後の id から 1 を引きます。
画像が DB に挿入されるようになったので、これらの画像を出力できます。ただし画像を扱うために、simpleImage と名付ける別のクラスの下準備をします。class フォルダに新しいファイル simpleImage.php を作り、そこにクラスのコードを書いていきます。
simpleImage.php ファイルを開き、次のように書きます:
<?php
class simpleCMS {
}
?>
さて、画像を扱う新しいクラスを作ったので、次にそれを扱うメソッドを作る必要があります。しかしまず、PHP のクラス変数の種類について説明します。
PHP 5 のクラスプロパティ
以前の PHP4 ではすべてが単純で、クラスのプロパティは var として定義されていましたが、PHP 5 版ではクラスプロパティの定義により多くの自由が生まれました。すでに使ったので、public 演算子から始めましょう。
Public
public 演算子を通じて、開かれた、公にアクセス可能なクラス変数を宣言します。こうして、作成したオブジェクトでこの変数の値を変更でき、必要な値を代入するだけです。例えばこのように:
<?php
$obj = new simpleCMS();
$obj->db='newDB';
?>
このように、オブジェクトの変数にアクセスできます。繰り返しますが、変数が public として定義されたからです。
Protected
protected 演算子は、プロパティやメソッドがクラス内部、またはその派生クラスからアクセス可能であることを示します(継承と派生クラスについては後で話します)。今 db プロパティを protected として定義し、このプロパティをコードで呼び出すと、エラーが発生します:
simpleCMS.php
...
protected $db = 'testDB';
...
index.php
...
$obj = new simpleCMS();
$obj->db='newDB';
...
では、後でクラスのメソッドで使うために、保護された変数 path(ファイルへのパス)を追加しましょう。ただし、ファイルへのパスを渡すためにコンストラクタを使います。
クラスのコンストラクタ
クラスのコンストラクタは、作成されるオブジェクトに初期状態を設定するために PHP で使われます。例えば、作成される画像オブジェクトにファイルへのパスを設定します。コンストラクタは他のすべてのメソッドと同じように書き、その名前の先頭にアンダースコアを 2 つ付けます:
class simpleCMS {
protected $path;
public __construct($image){
$this->path = $image;
}
}
こうして、コンストラクタのパラメーターを通じてファイルへのパスを渡し、画像へのパスの保護された変数に記録します。次に、画像に関するデータが出力されるよう、simpleCMS クラスの public_display メソッドを変更する必要があります。
if(!empty($row['image_fid'])){
$sql = "SELECT * FROM Files WHERE fid=".$row['image_fid'];
$image_query = mysql_query($sql) or die(mysql_error());
$image = mysql_fetch_array($image_query);
$content .= '<div class="image">';
$image = new simpleImage($image['filepath']);
$content .= $image->scale(100,100);
$content .= '</div>';
unset($image);
}
こうして DB からファイルへのパスを出力します。ファイルへのパスを出力したので、画像のサイズを処理するメソッドを書きましょう。Drupal のような関数を作ります:
public function scale($width,$height){
}
public function scale_and_crop($width,$height){
}
まず、スケーリング関数 scale() から始めましょう。
public function scale($width, $height){
$path = $this->path;
$filepath = explode('/',$this->path); // フォルダとファイル名を分ける
$filename = end($filepath); // 最後の要素、つまりファイル名を取る
$filename = substr($filename, 0, -4); // ファイル名から拡張子を除く
$filename = str_replace(' ', '_', $filename); // ファイル名からスペースを除く
$extenstion = substr($this->path, -4); // ファイルの拡張子 png, gif, jpg を確認する
switch ($extenstion){
case '.png':
$srcImage = ImageCreateFromPNG( $path );
break;
case '.jpg':
$srcImage = ImageCreateFromJPEG( $path );
break;
case '.gif':
$srcImage = ImageCreateFromGIF( $path );
break;
default:
$srcImage = ImageCreateFromGIF( $path );
break;
}
// 画像の元の高さと幅を求める
$srcWidth = ImageSX( $srcImage );
$srcHeight = ImageSY( $srcImage );
// 次のコードは、リサイズ時に正しい比率が保たれるよう、
// 幅が高さより大きいか、
// 高さが幅より大きいかを確認する
$ratioWidth = $srcWidth/$width;
$ratioHeight = $srcHeight/$height;
if( $ratioWidth < $ratioHeight){
$destWidth = $srcWidth/$ratioHeight;
$destHeight = $height;
}else{
$destWidth = $width;
$destHeight = $srcHeight/$ratioWidth;
}
// 最終的な幅と高さのデータで新しい画像を作る
print $destWidth. '<br />' .$destHeight;
$destImage = imagecreate( $destWidth, $destHeight);
// srcImage(元)を destImage(先)にコピーする
ImageCopyResized( $destImage, $srcImage, 0, 0, 0, 0, $destWidth, $destHeight, $srcWidth, $srcHeight );
// jpeg を作る
imagejpeg( $destImage, 'files/presets/'.$width.'_'.$height.'_'. $filename . '.jpg' ,100 ); // files フォルダに presets フォルダを作る必要がある
$newImagePath = 'files/presets/'.$width.'_'.$height.'_'. $filename . '.jpg';
// メモリを解放する
ImageDestroy( $srcImage );
ImageDestroy( $destImage );
return '<img src="'. $newImagePath . '" width="'. $destWidth.'" height="'. $destHeight . '" />'; // 画像を出力する
}
処理された画像のファイルは files/presets フォルダに保存します。presets フォルダを作成し、そのフォルダにアクセス権 777 を付ける必要があります。
動作のアルゴリズムはコメントで詳しく説明したと思うので、scale_and_crop() メソッドに移りましょう。もっとも、scale() とはほとんど変わりません:
public function scale_and_crop($width, $height){
$path = $this->path;
$filepath = explode('/',$this->path); // フォルダとファイル名を分ける
$filename = end($filepath); // 最後の要素、つまりファイル名を取る
$filename = substr($filename, 0, -4); // ファイル名から拡張子を除く
$filename = str_replace(' ', '_', $filename); // ファイル名からスペースを除く
$extenstion = substr($this->path, -4); // ファイルの拡張子 png, gif, jpg を確認する
switch ($extenstion){
case '.png':
$srcImage = ImageCreateFromPNG( $path );
break;
case '.jpg':
$srcImage = ImageCreateFromJPEG( $path );
break;
case '.gif':
$srcImage = ImageCreateFromGIF( $path );
break;
default:
$srcImage = ImageCreateFromGIF( $path );
break;
}
// 画像の元の高さと幅を求める
$srcWidth = ImageSX( $srcImage );
$srcHeight = ImageSY( $srcImage );
// 次のコードは、リサイズ時に正しい比率が保たれるよう、
// 幅が高さより大きいか、
// 高さが幅より大きいかを確認する
$ratioSource = $srcWidth/$srcHeight;
$ratioNew = $width/$height;
if( $ratioSource < $ratioNew){
$newWidth = $srcWidth;
$newHeight = $srcHeight / $ratioNew * $ratioSource; // 指定した幅に比例して新しい画像の高さを計算する
}else{
$newWidth = $srcWidth / $ratioSource * $ratioNew; // 指定した高さに比例して新しい画像の幅を計算する
$newHeight = $srcHeight;
}
// 最終的な幅と高さのデータで新しい画像を作る
$destImage = imagecreate($width, $height); // scale() との違い
// srcImage(元)を destImage(先)にコピーする
imagecopyresampled( $destImage, $srcImage, 0, 0, 0, 0, $width, $height, $newWidth, $newHeight);
// jpeg を作る
imagejpeg( $destImage, 'files/presets/'.$width.'_'.$height.'_'. $filename . '.jpg' ,100 ); // files フォルダに presets フォルダを作る必要がある
$newImagePath = 'files/presets/'.$width.'_'.$height.'_'. $filename . '.jpg';
// メモリを解放する
ImageDestroy( $srcImage );
ImageDestroy( $destImage );
return '<img src="'. $newImagePath . '" />'; // 画像を出力する
}
また、display_public() メソッドの出力行も変更する必要があります:
$content .= '<div class="image">';
$image = new simpleImage($image['filepath']);
$content .= $image->scale_and_crop(100,100);
$content .= '</div>';
こうすると、画像は比率を保つために正方形に切り抜かれます。
1 つ目のケースでも 2 つ目のケースでも、画像はその場で生成されました。画像の生成には時間がかかるので、通常は縮小画像へのパスを DB に記憶し、画像を再生成せずにそのパスをすぐに出力します。
このコースではその方法は扱いません。自分の CMS の構築では Zend framework を使いますし、これらの PHP レッスンは何よりまず、あなたに PHP の動作の基礎を紹介するものだからです。