logo

パレット - カラフルに🎨

Palette — ビジュアルページビルダー、デザインの専門知識は不要です。

ライブデモ パレットをダウンロード

Scroll

PHP レッスン — レッスン 4 — 画像の処理、GD2 ライブラリ

08/12/2019, by Ivan

前のレッスンで 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 の動作の基礎を紹介するものだからです。