MediaWiki
REL1_19
|
00001 <?php 00002 00027 class RandomImageGenerator { 00028 00029 private $dictionaryFile; 00030 private $minWidth = 400 ; 00031 private $maxWidth = 800 ; 00032 private $minHeight = 400 ; 00033 private $maxHeight = 800 ; 00034 private $shapesToDraw = 5 ; 00035 00042 private static $orientations = array( 00043 array( 00044 '0thRow' => 'top', 00045 '0thCol' => 'left', 00046 'exifCode' => 1, 00047 'counterRotation' => array( array( 1, 0 ), array( 0, 1 ) ) 00048 ), 00049 array( 00050 '0thRow' => 'bottom', 00051 '0thCol' => 'right', 00052 'exifCode' => 3, 00053 'counterRotation' => array( array( -1, 0 ), array( 0, -1 ) ) 00054 ), 00055 array( 00056 '0thRow' => 'right', 00057 '0thCol' => 'top', 00058 'exifCode' => 6, 00059 'counterRotation' => array( array( 0, 1 ), array( 1, 0 ) ) 00060 ), 00061 array( 00062 '0thRow' => 'left', 00063 '0thCol' => 'bottom', 00064 'exifCode' => 8, 00065 'counterRotation' => array( array( 0, -1 ), array( -1, 0 ) ) 00066 ) 00067 ); 00068 00069 00070 public function __construct( $options = array() ) { 00071 foreach ( array( 'dictionaryFile', 'minWidth', 'minHeight', 'maxWidth', 'maxHeight', 'shapesToDraw' ) as $property ) { 00072 if ( isset( $options[$property] ) ) { 00073 $this->$property = $options[$property]; 00074 } 00075 } 00076 00077 // find the dictionary file, to generate random names 00078 if ( !isset( $this->dictionaryFile ) ) { 00079 foreach ( array( 00080 '/usr/share/dict/words', 00081 '/usr/dict/words', 00082 dirname( __FILE__ ) . '/words.txt' ) 00083 as $dictionaryFile ) { 00084 if ( is_file( $dictionaryFile ) and is_readable( $dictionaryFile ) ) { 00085 $this->dictionaryFile = $dictionaryFile; 00086 break; 00087 } 00088 } 00089 } 00090 if ( !isset( $this->dictionaryFile ) ) { 00091 throw new Exception( "RandomImageGenerator: dictionary file not found or not specified properly" ); 00092 } 00093 } 00094 00103 function writeImages( $number, $format = 'jpg', $dir = null ) { 00104 $filenames = $this->getRandomFilenames( $number, $format, $dir ); 00105 $imageWriteMethod = $this->getImageWriteMethod( $format ); 00106 foreach( $filenames as $filename ) { 00107 $this->{$imageWriteMethod}( $this->getImageSpec(), $format, $filename ); 00108 } 00109 return $filenames; 00110 } 00111 00112 00117 function getImageWriteMethod( $format ) { 00118 global $wgUseImageMagick, $wgImageMagickConvertCommand; 00119 if ( $format === 'svg' ) { 00120 return 'writeSvg'; 00121 } else { 00122 // figure out how to write images 00123 global $wgExiv2Command; 00124 if ( class_exists( 'Imagick' ) && $wgExiv2Command && is_executable( $wgExiv2Command ) ) { 00125 return 'writeImageWithApi'; 00126 } elseif ( $wgUseImageMagick && $wgImageMagickConvertCommand && is_executable( $wgImageMagickConvertCommand ) ) { 00127 return 'writeImageWithCommandLine'; 00128 } 00129 } 00130 throw new Exception( "RandomImageGenerator: could not find a suitable method to write images in '$format' format" ); 00131 } 00132 00142 private function getRandomFilenames( $number, $extension = 'jpg', $dir = null ) { 00143 if ( is_null( $dir ) ) { 00144 $dir = getcwd(); 00145 } 00146 $filenames = array(); 00147 foreach( $this->getRandomWordPairs( $number ) as $pair ) { 00148 $basename = $pair[0] . '_' . $pair[1]; 00149 if ( !is_null( $extension ) ) { 00150 $basename .= '.' . $extension; 00151 } 00152 $basename = preg_replace( '/\s+/', '', $basename ); 00153 $filenames[] = "$dir/$basename"; 00154 } 00155 00156 return $filenames; 00157 00158 } 00159 00160 00167 public function getImageSpec() { 00168 $spec = array(); 00169 00170 $spec['width'] = mt_rand( $this->minWidth, $this->maxWidth ); 00171 $spec['height'] = mt_rand( $this->minHeight, $this->maxHeight ); 00172 $spec['fill'] = $this->getRandomColor(); 00173 00174 $diagonalLength = sqrt( pow( $spec['width'], 2 ) + pow( $spec['height'], 2 ) ); 00175 00176 $draws = array(); 00177 for ( $i = 0; $i <= $this->shapesToDraw; $i++ ) { 00178 $radius = mt_rand( 0, $diagonalLength / 4 ); 00179 if ( $radius == 0 ) { 00180 continue; 00181 } 00182 $originX = mt_rand( -1 * $radius, $spec['width'] + $radius ); 00183 $originY = mt_rand( -1 * $radius, $spec['height'] + $radius ); 00184 $angle = mt_rand( 0, ( 3.141592/2 ) * $radius ) / $radius; 00185 $legDeltaX = round( $radius * sin( $angle ) ); 00186 $legDeltaY = round( $radius * cos( $angle ) ); 00187 00188 $draw = array(); 00189 $draw['fill'] = $this->getRandomColor(); 00190 $draw['shape'] = array( 00191 array( 'x' => $originX, 'y' => $originY - $radius ), 00192 array( 'x' => $originX + $legDeltaX, 'y' => $originY + $legDeltaY ), 00193 array( 'x' => $originX - $legDeltaX, 'y' => $originY + $legDeltaY ), 00194 array( 'x' => $originX, 'y' => $originY - $radius ) 00195 ); 00196 $draws[] = $draw; 00197 00198 } 00199 00200 $spec['draws'] = $draws; 00201 00202 return $spec; 00203 } 00204 00212 static function shapePointsToString( $shape ) { 00213 $points = array(); 00214 foreach ( $shape as $point ) { 00215 $points[] = $point['x'] . ',' . $point['y']; 00216 } 00217 return join( " ", $points ); 00218 } 00219 00227 public function writeSvg( $spec, $format, $filename ) { 00228 $svg = new SimpleXmlElement( '<svg/>' ); 00229 $svg->addAttribute( 'xmlns', 'http://www.w3.org/2000/svg' ); 00230 $svg->addAttribute( 'version', '1.1' ); 00231 $svg->addAttribute( 'width', $spec['width'] ); 00232 $svg->addAttribute( 'height', $spec['height'] ); 00233 $g = $svg->addChild( 'g' ); 00234 foreach ( $spec['draws'] as $drawSpec ) { 00235 $shape = $g->addChild( 'polygon' ); 00236 $shape->addAttribute( 'fill', $drawSpec['fill'] ); 00237 $shape->addAttribute( 'points', self::shapePointsToString( $drawSpec['shape'] ) ); 00238 }; 00239 if ( ! $fh = fopen( $filename, 'w' ) ) { 00240 throw new Exception( "couldn't open $filename for writing" ); 00241 } 00242 fwrite( $fh, $svg->asXML() ); 00243 if ( !fclose($fh) ) { 00244 throw new Exception( "couldn't close $filename" ); 00245 } 00246 } 00247 00254 public function writeImageWithApi( $spec, $format, $filename ) { 00255 // this is a hack because I can't get setImageOrientation() to work. See below. 00256 global $wgExiv2Command; 00257 00258 $image = new Imagick(); 00263 $orientation = self::$orientations[0]; // default is normal orientation 00264 if ( $format == 'jpg' ) { 00265 $orientation = self::$orientations[ array_rand( self::$orientations ) ]; 00266 $spec = self::rotateImageSpec( $spec, $orientation['counterRotation'] ); 00267 } 00268 00269 $image->newImage( $spec['width'], $spec['height'], new ImagickPixel( $spec['fill'] ) ); 00270 00271 foreach ( $spec['draws'] as $drawSpec ) { 00272 $draw = new ImagickDraw(); 00273 $draw->setFillColor( $drawSpec['fill'] ); 00274 $draw->polygon( $drawSpec['shape'] ); 00275 $image->drawImage( $draw ); 00276 } 00277 00278 $image->setImageFormat( $format ); 00279 00280 // this doesn't work, even though it's documented to do so... 00281 // $image->setImageOrientation( $orientation['exifCode'] ); 00282 00283 $image->writeImage( $filename ); 00284 00285 // because the above setImageOrientation call doesn't work... nor can I get an external imagemagick binary to do this either... 00286 // hacking this for now (only works if you have exiv2 installed, a program to read and manipulate exif) 00287 if ( $wgExiv2Command ) { 00288 $cmd = wfEscapeShellArg( $wgExiv2Command ) 00289 . " -M " 00290 . wfEscapeShellArg( "set Exif.Image.Orientation " . $orientation['exifCode'] ) 00291 . " " 00292 . wfEscapeShellArg( $filename ); 00293 00294 $retval = 0; 00295 $err = wfShellExec( $cmd, $retval ); 00296 if ( $retval !== 0 ) { 00297 print "Error with $cmd: $retval, $err\n"; 00298 } 00299 } 00300 } 00301 00309 private static function rotateImageSpec( &$spec, $matrix ) { 00310 $tSpec = array(); 00311 $dims = self::matrixMultiply2x2( $matrix, $spec['width'], $spec['height'] ); 00312 $correctionX = 0; 00313 $correctionY = 0; 00314 if ( $dims['x'] < 0 ) { 00315 $correctionX = abs( $dims['x'] ); 00316 } 00317 if ( $dims['y'] < 0 ) { 00318 $correctionY = abs( $dims['y'] ); 00319 } 00320 $tSpec['width'] = abs( $dims['x'] ); 00321 $tSpec['height'] = abs( $dims['y'] ); 00322 $tSpec['fill'] = $spec['fill']; 00323 $tSpec['draws'] = array(); 00324 foreach( $spec['draws'] as $draw ) { 00325 $tDraw = array( 00326 'fill' => $draw['fill'], 00327 'shape' => array() 00328 ); 00329 foreach( $draw['shape'] as $point ) { 00330 $tPoint = self::matrixMultiply2x2( $matrix, $point['x'], $point['y'] ); 00331 $tPoint['x'] += $correctionX; 00332 $tPoint['y'] += $correctionY; 00333 $tDraw['shape'][] = $tPoint; 00334 } 00335 $tSpec['draws'][] = $tDraw; 00336 } 00337 return $tSpec; 00338 } 00339 00347 private static function matrixMultiply2x2( $matrix, $x, $y ) { 00348 return array( 00349 'x' => $x * $matrix[0][0] + $y * $matrix[0][1], 00350 'y' => $x * $matrix[1][0] + $y * $matrix[1][1] 00351 ); 00352 } 00353 00354 00368 public function writeImageWithCommandLine( $spec, $format, $filename ) { 00369 global $wgImageMagickConvertCommand; 00370 $args = array(); 00371 $args[] = "-size " . wfEscapeShellArg( $spec['width'] . 'x' . $spec['height'] ); 00372 $args[] = wfEscapeShellArg( "xc:" . $spec['fill'] ); 00373 foreach( $spec['draws'] as $draw ) { 00374 $fill = $draw['fill']; 00375 $polygon = self::shapePointsToString( $draw['shape'] ); 00376 $drawCommand = "fill $fill polygon $polygon"; 00377 $args[] = '-draw ' . wfEscapeShellArg( $drawCommand ); 00378 } 00379 $args[] = wfEscapeShellArg( $filename ); 00380 00381 $command = wfEscapeShellArg( $wgImageMagickConvertCommand ) . " " . implode( " ", $args ); 00382 $retval = null; 00383 wfShellExec( $command, $retval ); 00384 return ( $retval === 0 ); 00385 } 00386 00392 public function getRandomColor() { 00393 $components = array(); 00394 for ($i = 0; $i <= 2; $i++ ) { 00395 $components[] = mt_rand( 0, 255 ); 00396 } 00397 return 'rgb(' . join(', ', $components) . ')'; 00398 } 00399 00406 private function getRandomWordPairs( $number ) { 00407 $lines = $this->getRandomLines( $number * 2 ); 00408 // construct pairs of words 00409 $pairs = array(); 00410 $count = count( $lines ); 00411 for( $i = 0; $i < $count; $i += 2 ) { 00412 $pairs[] = array( $lines[$i], $lines[$i+1] ); 00413 } 00414 return $pairs; 00415 } 00416 00417 00426 private function getRandomLines( $number_desired ) { 00427 $filepath = $this->dictionaryFile; 00428 00429 // initialize array of lines 00430 $lines = array(); 00431 for ( $i = 0; $i < $number_desired; $i++ ) { 00432 $lines[] = null; 00433 } 00434 00435 /* 00436 * This algorithm obtains N random lines from a file in one single pass. It does this by replacing elements of 00437 * a fixed-size array of lines, less and less frequently as it reads the file. 00438 */ 00439 $fh = fopen( $filepath, "r" ); 00440 if ( !$fh ) { 00441 throw new Exception( "couldn't open $filepath" ); 00442 } 00443 $line_number = 0; 00444 $max_index = $number_desired - 1; 00445 while( !feof( $fh ) ) { 00446 $line = fgets( $fh ); 00447 if ( $line !== false ) { 00448 $line_number++; 00449 $line = trim( $line ); 00450 if ( mt_rand( 0, $line_number ) <= $max_index ) { 00451 $lines[ mt_rand( 0, $max_index ) ] = $line; 00452 } 00453 } 00454 } 00455 fclose( $fh ); 00456 if ( $line_number < $number_desired ) { 00457 throw new Exception( "not enough lines in $filepath" ); 00458 } 00459 00460 return $lines; 00461 } 00462 00463 }