Advertisement
Guest User

jeancaffou

a guest
May 12th, 2009
2,627
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 13.99 KB | None | 0 0
  1. <?php
  2. /**
  3. * Purpose: A photographic mosaic generator
  4. * Description: Gathers information about images and composes a photographic mosaic
  5. *
  6. * @version Date: 1. may 2009
  7. * @author Žan Kafol
  8. * @access public
  9. */
  10.  
  11. debug('--- STARTING MOSAIC ---');
  12.  
  13. /*
  14.  * Set system boundaries and error reporting
  15.  */
  16.  
  17. error_reporting(E_ALL);
  18. set_time_limit(0);
  19. ini_set('memory_limit','3G');
  20.  
  21. /*
  22.  * Some parameters...
  23.  */
  24.  
  25. //how big is the 'pixel' or piece of the composed mosaic image
  26. $piece_w = 256;
  27. $piece_h = 256;
  28.  
  29. //smaller scale of the template in pixels (in large scale this is $mosaic_w*$piece_w)
  30. $mosaic_w = 30;
  31. $mosaic_h = 45;
  32.  
  33. //algorithm for the average pixel scan (1=arithmetic_mean,2=harmonic_mean,3=median,4=modal_score,5=resample(),6=resize)
  34. $average_type = 5;
  35. //override this setting if it's passed as an argument
  36. if(count($argv)>1) $average_type = intval($argv[1]);
  37.  
  38. //jpeg compression quality [0..100], where 100 is best quality
  39. $quality = 90;
  40.  
  41. //Where are your images stored (will be scaned recursively for .jpg files)
  42. $imgdir = "xx";
  43.  
  44. //Template image (will be resized to $mosaic_w x $mosaic_h)
  45. $source_img = "20090409-_MG_6779.jpg";
  46.  
  47. //Filename of the generated mosaic
  48. $output_image = "mosaic_$average_type.jpg";
  49.  
  50. //Index cache filename
  51. $cache_idx = "cachergb-$average_type.txt";
  52.  
  53. //Cache in case something crashes
  54. $generated_idx = "composit-$source_img-$mosaic_w-$mosaic_h-$piece_w-$piece_h-$average_type.txt";
  55.  
  56. //Cache smaller pieces (useful if your images are as-is, and once it has cache, this really speeds things up)
  57. $piece_cache = "pieces";
  58.  
  59. /*
  60.  * Start processing!
  61.  *
  62.  * a brief explanation on how this works:
  63.  * - Get all available images (their relative filenames) in the array
  64.  * - Loop through the array of images, extract their 'average pixel' - a one-color representation of the whole image in a 4D array
  65.  *      where the first three dimensions are the [R][G][B] colorspace and the fourth is the array of image filenames that represent
  66.  *      the current RGB color. Cache the array to disk, as the scan is intensive and the array is easily reused for a new photographic mosaic
  67.  * - When we have our available pixel colors and their image representation in the array, we can begin building our mosaic.
  68.  *      We take our template image and shrink it way down, because every pixel in that template will be an image. Loop through pixels, find the
  69.  *      best approximation to the pixel color we have in our RGB array (there is little probability, that we have the exact pixel), and paste
  70.  *      the image to the coordinate in the large-scale mosaic relative to the pixel in the template
  71.  */
  72.  
  73. debug("average type set to $average_type !");
  74. debug('Getting available images...');
  75. //recursively scan all images in the directory and save them in the array
  76. $all_images = get_images($imgdir);
  77. if(!$all_images) {
  78.     debug("invalid directory");
  79.     die;
  80. }
  81.  
  82. //Read indexes from cache - check if images exists
  83. $_rgb_indexes = uncache_var($cache_idx);
  84. foreach($_rgb_indexes as $r=>$gb_idx) {
  85.     foreach($gb_idx as $g=>$b_idx) {
  86.         foreach($b_idx as $b=>$images) {
  87.             foreach($images as $image) {
  88.                 if(file_exists($image)) {
  89.                     $rgb_indexes[$r][$g][$b][] = $image;
  90.                     $cached[$image] = true;
  91.                 } else {
  92.                     debug("!!! Indexed file [$image] does not exist anymore!");
  93.                 }
  94.             }
  95.         }
  96.     }
  97. }
  98.  
  99. //Index all images, and skip the ones that are cached, add the ones that are not cached (new)
  100. debug('Indexing '.count($all_images).' images for average pixel...');
  101. $bfin = $proctime = 1;
  102. foreach($all_images as $i=>$image) {
  103.     if(!isset($cached[$image]) || !$cached[$image]) {
  104.         $proctime = microtime(true);
  105.        
  106.         $piece = generate_piece($image,$piece_w,$piece_h,$piece_cache,$quality);
  107.        
  108.         if(!$piece) {
  109.             debug("something went wrong!");
  110.             die;
  111.         }
  112.        
  113.         list($r,$g,$b) = get_average_pixel($piece,$average_type);
  114.         imagedestroy($piece);
  115.        
  116.         $rgb_indexes[$r][$g][$b][] = $image;
  117.         $cached[$image] = true;
  118.         $n_pix = count($rgb_indexes[$r][$g][$b]);
  119.        
  120.         cache_var($rgb_indexes,$cache_idx);
  121.        
  122.         $percent = $i/count($all_images);
  123.         $proctime = microtime(true) - $proctime;
  124.         $fin = percent($percent);
  125.         $eta = duration(($proctime*(1-$percent)) / ($percent-$bfin));
  126.         debug("INDEX $image [$fin%] [ETA: $eta] [avgpx($r,$g,$b)] [c: $n_pix]");
  127.         $bfin = $percent;
  128.         $bproctime = $proctime;
  129.     }
  130. }
  131. cache_var($rgb_indexes,$cache_idx);
  132.  
  133.  
  134. //Prepare the template and allocate the memory for the mosaic
  135. debug('Preparing for composit...');
  136. if(!file_exists($source_img)) {
  137.     debug('template does not exist!');
  138.     die;
  139. }
  140. $original = imagecreatefromjpeg($source_img);
  141. $template = imagecreatetruecolor($mosaic_w,$mosaic_h);
  142. imagecopyresampled($template,$original,0,0,0,0,$mosaic_w,$mosaic_h,imagesx($original),imagesy($original));
  143. imagedestroy($original);
  144.  
  145. debug('Checking for cached composit...');
  146. $gen_pieces = uncache_var($generated_idx);
  147. if(count($gen_pieces)>0) {
  148.     debug('Opening cached composit...');
  149.     $mosaic = imagecreatefromjpeg($output_image);
  150.     if(!$mosaic) {
  151.         debug("corrupt index and/or dumped file! cleaning up...");
  152.         if(file_exists($generated_idx)) unlink($generated_idx);
  153.         debug("restart the composit! aborting...");
  154.         die;
  155.     }
  156. } else {
  157.     debug('Allocating memory for the new image...');
  158.     $mosaic = imagecreatetruecolor($mosaic_w*$piece_w,$mosaic_h*$piece_h);
  159. }
  160.  
  161. //Start putting photos on the image as they were pixels
  162. debug('Generating composition ...');
  163. $loops = 0;
  164. for($i=0;$i<$mosaic_w;$i++) {
  165.     for($j=0;$j<$mosaic_h;$j++) {
  166.         //if this is a fix, skip pieces that are already generated
  167.         if(isset($gen_pieces[$i][$j]) && $gen_pieces[$i][$j]) {
  168.             debug("skipping pixel [$i][$j] ...");
  169.             continue;
  170.         }
  171.        
  172.         $proctime = microtime(true);
  173.        
  174.         list($r,$g,$b) = rgb(imagecolorat($template,$i,$j));
  175.        
  176.         //Find the best matching color
  177.         foreach($rgb_indexes as $x1=>$xi) {
  178.             foreach($xi as $y1=>$yi) {
  179.                 foreach($yi as $z1=>$zi) {
  180.                     $distance = distance($x1,$y1,$z1,$r,$g,$b);
  181.                     if(!isset($min_distance) || $distance<$min_distance) {
  182.                         $min_distance = $distance;
  183.                         $br = $x1;
  184.                         $bg = $y1;
  185.                         $bb = $z1;
  186.                     }
  187.                 }
  188.             }
  189.         }
  190.         //This estimate is preety bad
  191.         // $kbr = $kbg = $kbb = 999;
  192.         // foreach($rgb_indexes as $red_index => $rest_indexes) if(abs($r-$red_index) < abs($r-$kbr)) $kbr = $red_index;
  193.         // foreach($rgb_indexes[$kbr] as $green_index => $rest_indexes) if(abs($g-$green_index) < abs($g-$kbg)) $kbg = $green_index;
  194.         // foreach($rgb_indexes[$kbr][$kbg] as $kblue_index => $images) if(abs($b-$kblue_index) < abs($b-$kbb)) $kbb = $kblue_index;
  195.         // $prev_dist = distance($kbr,$kbg,$kbg,$r,$g,$b);
  196.        
  197.        
  198.         //At these RGB coordinates, we have one or more images. Choose a random one.
  199.         $best_match_pixels = $rgb_indexes[$br][$bg][$bb];
  200.         $n_matching = count($best_match_pixels)-1;
  201.         $r_match = rand(0,$n_matching);
  202.         $best_match = $best_match_pixels[$r_match];
  203.         $distincts[$best_match] = true;
  204.        
  205.         //Put the piece on the composit
  206.         $piece = generate_piece($best_match,$piece_w,$piece_h,$piece_cache,$quality);
  207.         $success = imagecopy($mosaic,$piece,$i*$piece_w,$j*$piece_h,0,0,$piece_w,$piece_h);
  208.         imagedestroy($piece);
  209.        
  210.         //Print some statistical data on the screen
  211.         $percent = ++$loops/($mosaic_w*$mosaic_h);
  212.         $proctime = microtime(true) - $proctime;
  213.         $eta = duration(($proctime*(1-$percent)) / ($percent-$bfin));
  214.         $fin = percent($percent);
  215.         $est = percent((distance(0,0,0,255,255,255)-$min_distance)/distance(0,0,0,255,255,255));
  216.         $ests[] = $est; $r_match++; $n_matching++;
  217.         debug("COMP [$fin%] [ETA: $eta] $est% match [$r_match/$n_matching] for ($r,$g,$b) to ($br,$bg,$bb) => $best_match");
  218.        
  219.         $bfin = $percent;
  220.         $bproctime = $proctime;
  221.         unset($min_distance);
  222.        
  223.         //Check if everything is successfull
  224.         if($success) {
  225.             $gen_pieces[$i][$j] = true;
  226.         } else {
  227.             debug("error in generating, chechk it out! dumping current composit...");
  228.             cache_var($gen_pieces,$generated_idx);
  229.             imagejpeg($mosaic,$output_image,$quality);
  230.             debug("abort!");
  231.             die;
  232.         }
  233.     }
  234. }
  235.  
  236. //Mosaic is generated, output it to disk and exit
  237. $distinct = count($distincts);
  238. imagedestroy($template);
  239. $est = arithmetic_mean($ests);
  240. debug("Saving generated image to $output_image (composed from $loops images, $distinct distinct photos) $est% match overall...");
  241. imagejpeg($mosaic,$output_image,$quality);
  242. debug('Saved! Unallocating memory...');
  243. imagedestroy($mosaic);
  244. if(file_exists($generated_idx)) unlink($generated_idx);
  245. debug('--- ALL DONE ---');
  246.  
  247. /*
  248.  * Functions
  249.  */
  250.  
  251. //Calculates Euclidian distance between two points in space (3-norm)
  252. function distance($x1,$y1,$z1,$x2,$y2,$z2) {
  253.     return pow(pow(abs($x1-$x2),3)+pow(abs($y1-$y2),3)+pow(abs($z1-$z2),3),1/3);
  254. }
  255.  
  256. //Calculates Euclidian distance between two points in p-norm
  257. function euclidian_distance($p1,$p2) {
  258.     $distances = 0;
  259.     $norm = (count($p1)+count($p2))/2;
  260.     for($i=0;$i<$norm;$i++) $distances += pow(abs($p1[$i]-$p2[$i]),$norm);
  261.     return pow($distances,1/$norm);
  262. }
  263.  
  264. //Reads the variable from disk
  265. function uncache_var($file) {
  266.     if(!file_exists($file)) return array();
  267.     $handle = fopen($file,'r');
  268.     $contents = fread($handle, filesize($file));
  269.     fclose($handle);
  270.     return unserialize($contents);
  271. }
  272.  
  273. //Saves the variable to disk
  274. function cache_var($var,$file) {
  275.     $fh = fopen($file,'w+');
  276.     fwrite($fh,serialize($var));
  277.     fclose($fh);
  278. }
  279.  
  280. //Generates the 'block' or 'pixel' of the mosaic
  281. function generate_piece($image,$piece_w,$piece_h,$cache=false,$quality=100) {
  282.     $sha = sha1($image);
  283.     $cfn = "$cache/$sha.jpg";
  284.    
  285.     //Check if cache of the piece exists
  286.     if($cache && file_exists($cfn)) {
  287.         return imagecreatefromjpeg($cfn);
  288.     }
  289.    
  290.     $original = imagecreatefromjpeg($image);
  291.     $original_min = min(imagesx($original),imagesy($original));
  292.    
  293.     //Take the center of crop
  294.     $src_x = (imagesx($original)-$original_min)/2;
  295.     $src_y = (imagesy($original)-$original_min)/2;
  296.    
  297.     //Shrink it down
  298.     $piece = imagecreatetruecolor($piece_w,$piece_h);
  299.     $success = imagecopyresampled($piece,$original,0,0,$src_x,$src_y,$piece_w,$piece_h,$original_min,$original_min);
  300.     imagedestroy($original);
  301.    
  302.     //Cache it
  303.     if($cache && $success) {
  304.         if(!is_dir($cache)) mkdir($cache);
  305.         imagejpeg($piece,$cfn,$quality);
  306.     }
  307.    
  308.     return $success?$piece:false;
  309. }
  310.  
  311. //Returns a one-color representation of an image
  312. function get_average_pixel($im,$average_type) {
  313.     $width = imagesx($im);
  314.     $height = imagesy($im);
  315.     switch($average_type) {
  316.         case 1:
  317.         case 2:
  318.         case 3:
  319.         case 4:
  320.             $r = $g = $b = array();
  321.             for($i=0;$i<$width;$i++) {
  322.                 for($j=0;$j<$height;$j++) {
  323.                     $rgb = rgb(imagecolorat($im, $i, $j));
  324.                     $r[] = $rgb[0];
  325.                     $g[] = $rgb[1];
  326.                     $b[] = $rgb[2];
  327.                 }
  328.             }
  329.             return array(px_avg($r,$average_type),px_avg($g,$average_type),px_avg($b,$average_type));
  330.         case 5:
  331.             $onepx = imagecreatetruecolor(1,1);
  332.             imagecopyresampled($onepx,$im,0,0,0,0,1,1,$width,$height);
  333.             $rgb = imagecolorat($onepx,0,0);
  334.             imagedestroy($onepx);
  335.             return rgb($rgb);
  336.         case 6:
  337.             $onepx = imagecreatetruecolor(1,1);
  338.             imagecopyresized($onepx,$im,0,0,0,0,1,1,$width,$height);
  339.             $rgb = imagecolorat($onepx,0,0);
  340.             imagedestroy($onepx);
  341.             return rgb($rgb);
  342.     }
  343. }
  344.  
  345. //Calculates the average of an array
  346. function px_avg($a,$average_type) {
  347.     switch($average_type) {
  348.         case 1: return round(arithmetic_mean($a));
  349.         case 2: return round(harmonic_mean($a));
  350.         case 3: return round(median($a));
  351.         case 4: return round(modal_score($a));
  352.     }
  353. }
  354.  
  355. // List all 'jpg' files in a directory, recursively
  356. function get_images($folder) {
  357.     $files = array();
  358.     if(!is_dir($folder)) return false;
  359.     if ($handle = opendir($folder)) {
  360.         while (false !== ($file = readdir($handle))) {
  361.             if ($file != '.' && $file != '..') {
  362.                 if(strtolower(file_extension($file)) == 'jpg') {
  363.                     $files[] = "$folder/$file";
  364.                 }
  365.                 if(is_dir("$folder/$file")) {
  366.                     $recursive = get_images("$folder/$file");
  367.                     foreach($recursive as $rfile) $files[] = $rfile;
  368.                 }
  369.             }
  370.         }
  371.         closedir($handle);
  372.     }
  373.     return $files;
  374. }
  375.  
  376. // Get file extension
  377. function file_extension($filename) {
  378.     return end(explode('.', $filename));
  379. }
  380.  
  381. //Average functions
  382. function arithmetic_mean($a) {
  383.     return array_sum($a)/count($a);
  384. }
  385. function geometric_mean($a) {
  386.     $mul = 0.0;
  387.     foreach($a as $i=>$n) $mul = (float) (($i == 0) ? $n : $mul*$n);
  388.     return pow($mul,1/count($a));
  389. }
  390. function harmonic_mean($a) {
  391.     $sum = 0;
  392.     foreach($a as $n) $sum += ($n == 0) ? 0 : 1/$n;
  393.     return (1/$sum)*count($a);
  394. }
  395. function median($a) {
  396.     sort($a,SORT_NUMERIC);
  397.     return (count($a)%2 == 0) ?
  398.         $a[floor(count($a)/2)] :
  399.         ($a[count($a)/2] + $a[count($a)/2 - 1]) / 2;
  400. }
  401. function modal_score($a) {
  402.     $quant = array();
  403.     foreach($a as $n) {
  404.         if(isset($quant[$n])) {
  405.             $quant[$n]++;
  406.         } else {
  407.             $quant[$n] = 1;
  408.         }
  409.     }
  410.     $max = 0;
  411.     $mode = 0;
  412.     foreach($quant as $key=>$n) {
  413.         if($n>$max) {
  414.             $max = $n;
  415.             $mode = $key;
  416.         }
  417.     }
  418.     return $mode;
  419. }
  420.  
  421. //Returns array(R,G,B) of an (int) color (bit shift)
  422. function rgb($rgb) {
  423.     $r = ($rgb >> 16) & 0xFF;
  424.     $g = ($rgb >> 8) & 0xFF;
  425.     $b = $rgb & 0xFF;
  426.     return array($r,$g,$b);
  427. }
  428.  
  429. //Custom println() ;-)
  430. function debug($msg) {
  431.     print("[".udate("d.m.Y H:i:s.u")."] $msg\n");
  432. }
  433.  
  434. //Custom date function ;) - for milliseconds
  435. function udate($format, $utimestamp = null) {
  436.     if (is_null($utimestamp)) $utimestamp = microtime(true);
  437.     $timestamp = floor($utimestamp);
  438.     $milliseconds = round(($utimestamp - $timestamp) * 1000000);
  439.     return date(preg_replace('`(?<!\\\\)u`', $milliseconds, $format), $timestamp);
  440. }
  441.  
  442. //Returns time span duration
  443. function duration($seconds) {
  444.     $days = floor($seconds/60/60/24);
  445.     $hours = $seconds/60/60%24;
  446.     $mins = $seconds/60%60;
  447.     $secs = $seconds%60;
  448.  
  449.     $duration='';
  450.     if($days>0) $duration .= "{$days}d";
  451.     if($hours>0) $duration .= "{$hours}h";
  452.     if($mins>0) $duration .= "{$mins}m";
  453.     if($secs>0) $duration .= "{$secs}s";
  454.  
  455.     $duration = trim($duration);
  456.     if($duration==null) $duration = 'n/a';
  457.  
  458.     return $duration;
  459. }
  460.  
  461. //Returns percentage rounded to n decimal points
  462. function percent($percent,$decimal=2) {
  463.     return round($percent * 100 * pow(10,$decimal)) / pow(10,$decimal);
  464. }
  465. ?>
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement