[PHP]複数の画像をランダムに敷き詰めて一枚の画像に合成

random

フォルダに入った大量の写真をランダムなタイル状に並べて一枚の画像にしてみました。

画像をタイル一枚の最低サイズを満たすようにリサイズし、座標を shuffle でランダムにしています。
全種類並べ終えたらもう一度シャッフルして並べているので、画像のバリエーションが少なくても
すべての領域を埋め尽くすことができます。ちなみに例では25種類使っています。

<?php
//画像全体のサイズ
$width = 600;
$height = 600;

//タイル一枚の最低サイズ
$block_size = 100;

//画像ディレクトリ
$dir = "image/";

$list = scandir($dir);
$images = array();

foreach($list as $value){
	if(!is_file ($dir . $value)) continue;
	$images[] = load_image( $dir . $value, $block_size );
}

$max_x = ceil($width / $block_size);
$max_y = ceil($height / $block_size);

shuffle($images);

$x_range = range(0, $max_x);
$y_range = range(0, $max_y);

shuffle($x_range);
shuffle($y_range);

$points = array();

foreach($x_range as $x){
	foreach($y_range as $y){
		$points[] = array("x" => $x, "y" => $y);
	}
}

$canvas = imagecreatetruecolor($width, $height);

foreach($points as $point){
	$x = $point['x'];
	$y = $point['y'];
		$current = current($images);
		imagecopy(
			$canvas, $current['resource'],
			$x * $block_size - ( round($current['width'] / 2) ),
			$y * $block_size - ( round($current['height'] / 2) ), 
			0, 0, $current['width'], $current['height']
		);
		$res = next($images);
		if($res === false) shuffle($images);
}


header("Content-type:image/jpeg");
imagejpeg($canvas, null, 80);
imagedestroy($canvas);

foreach($images as &$image){
	imagedestroy($image['resource']);
}

exit;

function load_image($filepath, $block_size){
	$checkimg = getimagesize($filepath);
	$width = $checkimg[0];
	$height = $checkimg[1];
 
	if($checkimg['mime'] == "image/jpeg" || $checkimg['mime'] == "image/pjpeg"){
		$extension = "jpg";
	} else if ($checkimg['mime'] == "image/gif"){
		$extension = "gif";
	} else if ($checkimg['mime'] == "image/png" || $checkimg['mime'] == "image/x-png"){
		$extension = "png";
	} else {
		exit;
	}
 
	if($extension == 'jpg'){$image = ImageCreateFromJPEG($filepath);}
	if($extension == 'gif'){$image = ImageCreateFromGIF($filepath);}
	if($extension == 'png'){$image = ImageCreateFromPNG($filepath);}
	 
	
	$scale = $block_size / min($width, $height);
	$thumb_width  = $width * $scale;
	$thumb_height = $height * $scale;
	
	$thumb = imagecreatetruecolor($thumb_width, $thumb_height);
	imagecopyresampled($thumb, $image, 0, 0, 0, 0, $thumb_width, $thumb_height, $width, $height);
	 
	$info = array();
	$info['resource']	 = $thumb;
	$info['width']		 = $thumb_width;
	$info['height']		 = $thumb_height;
	$info['extension']	 = $extension;
	
	imagedestroy($image);
	
	return $info;
}

写真素材 足成 http://www.ashinari.com/

[PHP]str_replaceで複数置換する際に気をつけること

関数 str_replace() は、検索語句に配列を指定すると、複数の単語を一括で置換することができます。
しかし、この動作には少し癖があるので間違えると予期していない結果になることがあります。

仮に「AB」という文字と、「DC」という文字を全て「D」に置き換えたいときに、
次のようなコードを書いたとします。

<?php
$data = "ABC";
echo str_replace(array("AB", "DC"), "D", $data);
結果: D

期待される結果は「DC」となる予定でしたが、実際の結果は「D」だけになります。
まず「AB」が「D」に置き換えられ、文字列は「DC」になり、「DC」は「D」に置き換えられるので結局「D」だけが残ります。
要するに str_replace で複数の検索語句を指定した場合、最初から順番に置換を実行していくので、全て同時に置換するわけでは無いということです。
この動作は次のように書いた時と同じです。

<?php
$data = "ABC";
$data = str_replace("AB", "D", $data);
$data = str_replace("DC", "D", $data);
echo $data;
結果: D

元々の文字列には「DC」という単語はないにもかかわらず、最初の置換が実行された結果「DC」という単語ができてしまい、欲しい結果が得られません。
それを回避するには preg_replace を使うのが簡単です。

<?php
$data = "ABC";
echo preg_replace("/AB|DC/", "D", $data);
結果: DC

[PHP]似た画像を検索して近い順番に並べる(類似画像検索)

過去に類似画像を検索する方法はいくつか提示しましたが、おそらく今回の手法が一番多く使われているものです。
このプログラムでは、用意された画像をもとに、フォルダ内にある複数の画像の中から最も似た画像を探し、似ているものから順番に並べます。

原理はいたってシンプルで、比較元の画像と比較先の画像を小さな画像に変換し、1ピクセルずつ RGB 値を取得します。
あとは RGB を Lab 色空間上の座標に変換し、同じ座標同士の距離を比較し、近いものから順番にソートします。

RGB を Lab に変換するには一旦 xyz 表色系に変換し、それを Lab に変換する必要があります。
詳細については以前の記事を御覧下さい。

<?php
//比較元となる画像
$filepath = "sample.jpg";
 
//比較対象用画像ディレクトリ
$dir = "images/";

$sample = load_image($filepath);
$sample_lab = image_lab($sample);
imagedestroy($sample);

$list = scandir($dir);

$files = array();
foreach($list as $value){
	if(is_file($dir . $value)){
		$files[] = $dir . $value;
	}
}
$diff = array();
foreach($files as $file){
	$image	 = load_image($file);
	$lab	 = image_lab($image);
	imagedestroy($image);
	
	$name	 = basename($file);
	
	$distance = 0;
	foreach($sample_lab as $key => $value){
		$distance += lab_distance($value, $lab[$key]);
	}
	$diff[$name] = $distance;
}
 
asort($diff);
$result = array_keys($diff);
header("Content-type: text/html;charset=utf-8");
foreach($result as $img){
	echo '<img src="' . $dir . $img . '" width="50" height="50" alt="" />';
}

// 画像の読み込み
function load_image($filepath){
	$checkimg = getimagesize($filepath);
	if($checkimg['mime'] == "image/jpeg" || $checkimg['mime'] == "image/pjpeg"){
		$extension = "jpg";
	} else if ($checkimg['mime'] == "image/gif"){
		$extension = "gif";
	} else if ($checkimg['mime'] == "image/png" || $checkimg['mime'] == "image/x-png"){
		$extension = "png";
	} else {
		exit;
	}
 
	if($extension == 'jpg'){$image = ImageCreateFromJPEG($filepath);}
	if($extension == 'gif'){$image = ImageCreateFromGIF($filepath);}
	if($extension == 'png'){$image = ImageCreateFromPNG($filepath);}

	return $image;
}

// 画像をリサイズしピクセルごとのLab色空間上の座標を取得する
function image_lab($image){
	$width	 = imagesx($image);
	$height	 = imagesy($image);

	$thumb_width	 = 4;
	$thumb_height	 = 4;
	$thumb = imagecreatetruecolor($thumb_width, $thumb_height);
	imagecopyresampled($thumb, $image, 0, 0, 0, 0, $thumb_width, $thumb_height, $width, $height);
	 
	$lab = array();
	$red	 = 0;
	$green	 = 0;
	$blue	 = 0;
	 
	for($x=0; $x < $thumb_width; $x++){
		for($y=0; $y < $thumb_height; $y++){
			$index	 = imagecolorat($thumb, $x, $y);
			$rgb	 = imagecolorsforindex($thumb, $index);
			$lab[]	 = rgb2lab( array($rgb['red'], $rgb['green'], $rgb['blue']) );
		}
	}
 
	return $lab;
}

// xyz色空間上の座標をlab色空間上の座標に変換する
function xyz2lab($xyz) {
	$threshold = 0.008856;
	 
	//Chromatic Adaptation Matrices
	// D50
	$ref_x = 0.96422;
	$ref_y = 1.0000;
	$ref_z = 0.82521;

	$var_x = $xyz[0] / ($ref_x * 100);
	$var_y = $xyz[1] / ($ref_y * 100);		
	$var_z = $xyz[2] / ($ref_z * 100);
	 
	$var_x = ($var_x > $threshold) ? $var_x = pow($var_x, 1/3 ) : (7.787 * $var_x) + (16 / 116);
	$var_y = ($var_y > $threshold) ? $var_y = pow($var_y, 1/3 ) : (7.787 * $var_y) + (16 / 116);
	$var_z = ($var_z > $threshold) ? $var_z = pow($var_z, 1/3 ) : (7.787 * $var_z) + (16 / 116);
	 

	$l = ( 116 * $var_y ) - 16;
	$a = 500 * ( $var_x - $var_y );
	$b = 200 * ( $var_y - $var_z );
	$lab = array();
	 
	$lab = array($l, $a, $b);
	 
	return $lab;
}

// rgb値をxyz色空間上の座標に変換する
function rgb2xyz($rgb) {
	$r = $rgb[0] / 255;
	$g = $rgb[1] / 255;
	$b = $rgb[2] / 255;

	$r = ($r > 0.04045) ? pow(($r + 0.055) / 1.055, 2.4) : $r / 12.92;
	$g = ($g > 0.04045) ? pow(($g + 0.055) / 1.055, 2.4) : $g / 12.92;
	$b = ($b > 0.04045) ? pow(($b + 0.055) / 1.055, 2.4) : $b / 12.92;

	$r = $r * 100;
	$g = $g * 100;
	$b = $b * 100;
	 
	$xyz = array();
	 
	//sRGB D50
	$xyz[] = $r * 0.4360747 + $g * 0.3850649 + $b * 0.1430804;
	$xyz[] = $r * 0.2225045 + $g * 0.7168786 + $b * 0.0606169;
	$xyz[] = $r * 0.0139322 + $g * 0.0971045 + $b * 0.7141733;
	return $xyz;
}

// rgb値をlab色空間上の座標に変換する
function rgb2lab($rgb) {
	$xyz = rgb2xyz($rgb);
	$lab = xyz2lab($xyz);
	return $lab;
}

// 2つの座標を比較し距離を返す
function lab_distance($p1, $p2){
	$dist = sqrt( pow($p2[0] - $p1[0], 2) + pow($p2[1] - $p1[1], 2) + pow($p2[2] - $p1[2], 2) );
	return $dist;
}

このサンプルでは画像のサイズ( $thumb_width, $thumb_height )を 4×4 に統一したものを比較しています。
より厳密な一致を求めるならこの数字を大きくします。ただし計算量が多くなるので気をつけて下さい。