MediaWiki
REL1_24
|
00001 <?php 00030 class RandomImageGenerator { 00031 private $dictionaryFile; 00032 private $minWidth = 400; 00033 private $maxWidth = 800; 00034 private $minHeight = 400; 00035 private $maxHeight = 800; 00036 private $shapesToDraw = 5; 00037 00045 private static $orientations = array( 00046 array( 00047 '0thRow' => 'top', 00048 '0thCol' => 'left', 00049 'exifCode' => 1, 00050 'counterRotation' => array( array( 1, 0 ), array( 0, 1 ) ) 00051 ), 00052 array( 00053 '0thRow' => 'bottom', 00054 '0thCol' => 'right', 00055 'exifCode' => 3, 00056 'counterRotation' => array( array( -1, 0 ), array( 0, -1 ) ) 00057 ), 00058 array( 00059 '0thRow' => 'right', 00060 '0thCol' => 'top', 00061 'exifCode' => 6, 00062 'counterRotation' => array( array( 0, 1 ), array( 1, 0 ) ) 00063 ), 00064 array( 00065 '0thRow' => 'left', 00066 '0thCol' => 'bottom', 00067 'exifCode' => 8, 00068 'counterRotation' => array( array( 0, -1 ), array( -1, 0 ) ) 00069 ) 00070 ); 00071 00072 public function __construct( $options = array() ) { 00073 foreach ( array( 'dictionaryFile', 'minWidth', 'minHeight', 00074 'maxWidth', 'maxHeight', 'shapesToDraw' ) as $property 00075 ) { 00076 if ( isset( $options[$property] ) ) { 00077 $this->$property = $options[$property]; 00078 } 00079 } 00080 00081 // find the dictionary file, to generate random names 00082 if ( !isset( $this->dictionaryFile ) ) { 00083 foreach ( 00084 array( 00085 '/usr/share/dict/words', 00086 '/usr/dict/words', 00087 __DIR__ . '/words.txt' 00088 ) as $dictionaryFile 00089 ) { 00090 if ( is_file( $dictionaryFile ) and is_readable( $dictionaryFile ) ) { 00091 $this->dictionaryFile = $dictionaryFile; 00092 break; 00093 } 00094 } 00095 } 00096 if ( !isset( $this->dictionaryFile ) ) { 00097 throw new Exception( "RandomImageGenerator: dictionary file not " 00098 . "found or not specified properly" ); 00099 } 00100 } 00101 00111 function writeImages( $number, $format = 'jpg', $dir = null ) { 00112 $filenames = $this->getRandomFilenames( $number, $format, $dir ); 00113 $imageWriteMethod = $this->getImageWriteMethod( $format ); 00114 foreach ( $filenames as $filename ) { 00115 $this->{$imageWriteMethod}( $this->getImageSpec(), $format, $filename ); 00116 } 00117 00118 return $filenames; 00119 } 00120 00129 function getImageWriteMethod( $format ) { 00130 global $wgUseImageMagick, $wgImageMagickConvertCommand; 00131 if ( $format === 'svg' ) { 00132 return 'writeSvg'; 00133 } else { 00134 // figure out how to write images 00135 global $wgExiv2Command; 00136 if ( class_exists( 'Imagick' ) && $wgExiv2Command && is_executable( $wgExiv2Command ) ) { 00137 return 'writeImageWithApi'; 00138 } elseif ( $wgUseImageMagick 00139 && $wgImageMagickConvertCommand 00140 && is_executable( $wgImageMagickConvertCommand ) 00141 ) { 00142 return 'writeImageWithCommandLine'; 00143 } 00144 } 00145 throw new Exception( "RandomImageGenerator: could not find a suitable " 00146 . "method to write images in '$format' format" ); 00147 } 00148 00158 private function getRandomFilenames( $number, $extension = 'jpg', $dir = null ) { 00159 if ( is_null( $dir ) ) { 00160 $dir = getcwd(); 00161 } 00162 $filenames = array(); 00163 foreach ( $this->getRandomWordPairs( $number ) as $pair ) { 00164 $basename = $pair[0] . '_' . $pair[1]; 00165 if ( !is_null( $extension ) ) { 00166 $basename .= '.' . $extension; 00167 } 00168 $basename = preg_replace( '/\s+/', '', $basename ); 00169 $filenames[] = "$dir/$basename"; 00170 } 00171 00172 return $filenames; 00173 } 00174 00183 public function getImageSpec() { 00184 $spec = array(); 00185 00186 $spec['width'] = mt_rand( $this->minWidth, $this->maxWidth ); 00187 $spec['height'] = mt_rand( $this->minHeight, $this->maxHeight ); 00188 $spec['fill'] = $this->getRandomColor(); 00189 00190 $diagonalLength = sqrt( pow( $spec['width'], 2 ) + pow( $spec['height'], 2 ) ); 00191 00192 $draws = array(); 00193 for ( $i = 0; $i <= $this->shapesToDraw; $i++ ) { 00194 $radius = mt_rand( 0, $diagonalLength / 4 ); 00195 if ( $radius == 0 ) { 00196 continue; 00197 } 00198 $originX = mt_rand( -1 * $radius, $spec['width'] + $radius ); 00199 $originY = mt_rand( -1 * $radius, $spec['height'] + $radius ); 00200 $angle = mt_rand( 0, ( 3.141592 / 2 ) * $radius ) / $radius; 00201 $legDeltaX = round( $radius * sin( $angle ) ); 00202 $legDeltaY = round( $radius * cos( $angle ) ); 00203 00204 $draw = array(); 00205 $draw['fill'] = $this->getRandomColor(); 00206 $draw['shape'] = array( 00207 array( 'x' => $originX, 'y' => $originY - $radius ), 00208 array( 'x' => $originX + $legDeltaX, 'y' => $originY + $legDeltaY ), 00209 array( 'x' => $originX - $legDeltaX, 'y' => $originY + $legDeltaY ), 00210 array( 'x' => $originX, 'y' => $originY - $radius ) 00211 ); 00212 $draws[] = $draw; 00213 } 00214 00215 $spec['draws'] = $draws; 00216 00217 return $spec; 00218 } 00219 00227 static function shapePointsToString( $shape ) { 00228 $points = array(); 00229 foreach ( $shape as $point ) { 00230 $points[] = $point['x'] . ',' . $point['y']; 00231 } 00232 00233 return join( " ", $points ); 00234 } 00235 00246 public function writeSvg( $spec, $format, $filename ) { 00247 $svg = new SimpleXmlElement( '<svg/>' ); 00248 $svg->addAttribute( 'xmlns', 'http://www.w3.org/2000/svg' ); 00249 $svg->addAttribute( 'version', '1.1' ); 00250 $svg->addAttribute( 'width', $spec['width'] ); 00251 $svg->addAttribute( 'height', $spec['height'] ); 00252 $g = $svg->addChild( 'g' ); 00253 foreach ( $spec['draws'] as $drawSpec ) { 00254 $shape = $g->addChild( 'polygon' ); 00255 $shape->addAttribute( 'fill', $drawSpec['fill'] ); 00256 $shape->addAttribute( 'points', self::shapePointsToString( $drawSpec['shape'] ) ); 00257 } 00258 00259 if ( !$fh = fopen( $filename, 'w' ) ) { 00260 throw new Exception( "couldn't open $filename for writing" ); 00261 } 00262 fwrite( $fh, $svg->asXML() ); 00263 if ( !fclose( $fh ) ) { 00264 throw new Exception( "couldn't close $filename" ); 00265 } 00266 } 00267 00274 public function writeImageWithApi( $spec, $format, $filename ) { 00275 // this is a hack because I can't get setImageOrientation() to work. See below. 00276 global $wgExiv2Command; 00277 00278 $image = new Imagick(); 00285 $orientation = self::$orientations[0]; // default is normal orientation 00286 if ( $format == 'jpg' ) { 00287 $orientation = self::$orientations[array_rand( self::$orientations )]; 00288 $spec = self::rotateImageSpec( $spec, $orientation['counterRotation'] ); 00289 } 00290 00291 $image->newImage( $spec['width'], $spec['height'], new ImagickPixel( $spec['fill'] ) ); 00292 00293 foreach ( $spec['draws'] as $drawSpec ) { 00294 $draw = new ImagickDraw(); 00295 $draw->setFillColor( $drawSpec['fill'] ); 00296 $draw->polygon( $drawSpec['shape'] ); 00297 $image->drawImage( $draw ); 00298 } 00299 00300 $image->setImageFormat( $format ); 00301 00302 // this doesn't work, even though it's documented to do so... 00303 // $image->setImageOrientation( $orientation['exifCode'] ); 00304 00305 $image->writeImage( $filename ); 00306 00307 // because the above setImageOrientation call doesn't work... nor can I 00308 // get an external imagemagick binary to do this either... Hacking this 00309 // for now (only works if you have exiv2 installed, a program to read 00310 // and manipulate exif). 00311 if ( $wgExiv2Command ) { 00312 $cmd = wfEscapeShellArg( $wgExiv2Command ) 00313 . " -M " 00314 . wfEscapeShellArg( "set Exif.Image.Orientation " . $orientation['exifCode'] ) 00315 . " " 00316 . wfEscapeShellArg( $filename ); 00317 00318 $retval = 0; 00319 $err = wfShellExec( $cmd, $retval ); 00320 if ( $retval !== 0 ) { 00321 print "Error with $cmd: $retval, $err\n"; 00322 } 00323 } 00324 } 00325 00333 private static function rotateImageSpec( &$spec, $matrix ) { 00334 $tSpec = array(); 00335 $dims = self::matrixMultiply2x2( $matrix, $spec['width'], $spec['height'] ); 00336 $correctionX = 0; 00337 $correctionY = 0; 00338 if ( $dims['x'] < 0 ) { 00339 $correctionX = abs( $dims['x'] ); 00340 } 00341 if ( $dims['y'] < 0 ) { 00342 $correctionY = abs( $dims['y'] ); 00343 } 00344 $tSpec['width'] = abs( $dims['x'] ); 00345 $tSpec['height'] = abs( $dims['y'] ); 00346 $tSpec['fill'] = $spec['fill']; 00347 $tSpec['draws'] = array(); 00348 foreach ( $spec['draws'] as $draw ) { 00349 $tDraw = array( 00350 'fill' => $draw['fill'], 00351 'shape' => array() 00352 ); 00353 foreach ( $draw['shape'] as $point ) { 00354 $tPoint = self::matrixMultiply2x2( $matrix, $point['x'], $point['y'] ); 00355 $tPoint['x'] += $correctionX; 00356 $tPoint['y'] += $correctionY; 00357 $tDraw['shape'][] = $tPoint; 00358 } 00359 $tSpec['draws'][] = $tDraw; 00360 } 00361 00362 return $tSpec; 00363 } 00364 00372 private static function matrixMultiply2x2( $matrix, $x, $y ) { 00373 return array( 00374 'x' => $x * $matrix[0][0] + $y * $matrix[0][1], 00375 'y' => $x * $matrix[1][0] + $y * $matrix[1][1] 00376 ); 00377 } 00378 00396 public function writeImageWithCommandLine( $spec, $format, $filename ) { 00397 global $wgImageMagickConvertCommand; 00398 $args = array(); 00399 $args[] = "-size " . wfEscapeShellArg( $spec['width'] . 'x' . $spec['height'] ); 00400 $args[] = wfEscapeShellArg( "xc:" . $spec['fill'] ); 00401 foreach ( $spec['draws'] as $draw ) { 00402 $fill = $draw['fill']; 00403 $polygon = self::shapePointsToString( $draw['shape'] ); 00404 $drawCommand = "fill $fill polygon $polygon"; 00405 $args[] = '-draw ' . wfEscapeShellArg( $drawCommand ); 00406 } 00407 $args[] = wfEscapeShellArg( $filename ); 00408 00409 $command = wfEscapeShellArg( $wgImageMagickConvertCommand ) . " " . implode( " ", $args ); 00410 $retval = null; 00411 wfShellExec( $command, $retval ); 00412 00413 return ( $retval === 0 ); 00414 } 00415 00421 public function getRandomColor() { 00422 $components = array(); 00423 for ( $i = 0; $i <= 2; $i++ ) { 00424 $components[] = mt_rand( 0, 255 ); 00425 } 00426 00427 return 'rgb(' . join( ', ', $components ) . ')'; 00428 } 00429 00437 private function getRandomWordPairs( $number ) { 00438 $lines = $this->getRandomLines( $number * 2 ); 00439 // construct pairs of words 00440 $pairs = array(); 00441 $count = count( $lines ); 00442 for ( $i = 0; $i < $count; $i += 2 ) { 00443 $pairs[] = array( $lines[$i], $lines[$i + 1] ); 00444 } 00445 00446 return $pairs; 00447 } 00448 00459 private function getRandomLines( $number_desired ) { 00460 $filepath = $this->dictionaryFile; 00461 00462 // initialize array of lines 00463 $lines = array(); 00464 for ( $i = 0; $i < $number_desired; $i++ ) { 00465 $lines[] = null; 00466 } 00467 00468 /* 00469 * This algorithm obtains N random lines from a file in one single pass. 00470 * It does this by replacing elements of a fixed-size array of lines, 00471 * less and less frequently as it reads the file. 00472 */ 00473 $fh = fopen( $filepath, "r" ); 00474 if ( !$fh ) { 00475 throw new Exception( "couldn't open $filepath" ); 00476 } 00477 $line_number = 0; 00478 $max_index = $number_desired - 1; 00479 while ( !feof( $fh ) ) { 00480 $line = fgets( $fh ); 00481 if ( $line !== false ) { 00482 $line_number++; 00483 $line = trim( $line ); 00484 if ( mt_rand( 0, $line_number ) <= $max_index ) { 00485 $lines[mt_rand( 0, $max_index )] = $line; 00486 } 00487 } 00488 } 00489 fclose( $fh ); 00490 if ( $line_number < $number_desired ) { 00491 throw new Exception( "not enough lines in $filepath" ); 00492 } 00493 00494 return $lines; 00495 } 00496 }