MediaWiki
REL1_23
|
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 public function __construct( $options = array() ) { 00070 foreach ( array( 'dictionaryFile', 'minWidth', 'minHeight', 'maxWidth', 'maxHeight', 'shapesToDraw' ) as $property ) { 00071 if ( isset( $options[$property] ) ) { 00072 $this->$property = $options[$property]; 00073 } 00074 } 00075 00076 // find the dictionary file, to generate random names 00077 if ( !isset( $this->dictionaryFile ) ) { 00078 foreach ( 00079 array( 00080 '/usr/share/dict/words', 00081 '/usr/dict/words', 00082 __DIR__ . '/words.txt' 00083 ) as $dictionaryFile 00084 ) { 00085 if ( is_file( $dictionaryFile ) and is_readable( $dictionaryFile ) ) { 00086 $this->dictionaryFile = $dictionaryFile; 00087 break; 00088 } 00089 } 00090 } 00091 if ( !isset( $this->dictionaryFile ) ) { 00092 throw new Exception( "RandomImageGenerator: dictionary file not found or not specified properly" ); 00093 } 00094 } 00095 00104 function writeImages( $number, $format = 'jpg', $dir = null ) { 00105 $filenames = $this->getRandomFilenames( $number, $format, $dir ); 00106 $imageWriteMethod = $this->getImageWriteMethod( $format ); 00107 foreach ( $filenames as $filename ) { 00108 $this->{$imageWriteMethod}( $this->getImageSpec(), $format, $filename ); 00109 } 00110 00111 return $filenames; 00112 } 00113 00122 function getImageWriteMethod( $format ) { 00123 global $wgUseImageMagick, $wgImageMagickConvertCommand; 00124 if ( $format === 'svg' ) { 00125 return 'writeSvg'; 00126 } else { 00127 // figure out how to write images 00128 global $wgExiv2Command; 00129 if ( class_exists( 'Imagick' ) && $wgExiv2Command && is_executable( $wgExiv2Command ) ) { 00130 return 'writeImageWithApi'; 00131 } elseif ( $wgUseImageMagick && $wgImageMagickConvertCommand && is_executable( $wgImageMagickConvertCommand ) ) { 00132 return 'writeImageWithCommandLine'; 00133 } 00134 } 00135 throw new Exception( "RandomImageGenerator: could not find a suitable method to write images in '$format' format" ); 00136 } 00137 00147 private function getRandomFilenames( $number, $extension = 'jpg', $dir = null ) { 00148 if ( is_null( $dir ) ) { 00149 $dir = getcwd(); 00150 } 00151 $filenames = array(); 00152 foreach ( $this->getRandomWordPairs( $number ) as $pair ) { 00153 $basename = $pair[0] . '_' . $pair[1]; 00154 if ( !is_null( $extension ) ) { 00155 $basename .= '.' . $extension; 00156 } 00157 $basename = preg_replace( '/\s+/', '', $basename ); 00158 $filenames[] = "$dir/$basename"; 00159 } 00160 00161 return $filenames; 00162 } 00163 00170 public function getImageSpec() { 00171 $spec = array(); 00172 00173 $spec['width'] = mt_rand( $this->minWidth, $this->maxWidth ); 00174 $spec['height'] = mt_rand( $this->minHeight, $this->maxHeight ); 00175 $spec['fill'] = $this->getRandomColor(); 00176 00177 $diagonalLength = sqrt( pow( $spec['width'], 2 ) + pow( $spec['height'], 2 ) ); 00178 00179 $draws = array(); 00180 for ( $i = 0; $i <= $this->shapesToDraw; $i++ ) { 00181 $radius = mt_rand( 0, $diagonalLength / 4 ); 00182 if ( $radius == 0 ) { 00183 continue; 00184 } 00185 $originX = mt_rand( -1 * $radius, $spec['width'] + $radius ); 00186 $originY = mt_rand( -1 * $radius, $spec['height'] + $radius ); 00187 $angle = mt_rand( 0, ( 3.141592 / 2 ) * $radius ) / $radius; 00188 $legDeltaX = round( $radius * sin( $angle ) ); 00189 $legDeltaY = round( $radius * cos( $angle ) ); 00190 00191 $draw = array(); 00192 $draw['fill'] = $this->getRandomColor(); 00193 $draw['shape'] = array( 00194 array( 'x' => $originX, 'y' => $originY - $radius ), 00195 array( 'x' => $originX + $legDeltaX, 'y' => $originY + $legDeltaY ), 00196 array( 'x' => $originX - $legDeltaX, 'y' => $originY + $legDeltaY ), 00197 array( 'x' => $originX, 'y' => $originY - $radius ) 00198 ); 00199 $draws[] = $draw; 00200 } 00201 00202 $spec['draws'] = $draws; 00203 00204 return $spec; 00205 } 00206 00214 static function shapePointsToString( $shape ) { 00215 $points = array(); 00216 foreach ( $shape as $point ) { 00217 $points[] = $point['x'] . ',' . $point['y']; 00218 } 00219 00220 return join( " ", $points ); 00221 } 00222 00233 public function writeSvg( $spec, $format, $filename ) { 00234 $svg = new SimpleXmlElement( '<svg/>' ); 00235 $svg->addAttribute( 'xmlns', 'http://www.w3.org/2000/svg' ); 00236 $svg->addAttribute( 'version', '1.1' ); 00237 $svg->addAttribute( 'width', $spec['width'] ); 00238 $svg->addAttribute( 'height', $spec['height'] ); 00239 $g = $svg->addChild( 'g' ); 00240 foreach ( $spec['draws'] as $drawSpec ) { 00241 $shape = $g->addChild( 'polygon' ); 00242 $shape->addAttribute( 'fill', $drawSpec['fill'] ); 00243 $shape->addAttribute( 'points', self::shapePointsToString( $drawSpec['shape'] ) ); 00244 } 00245 00246 if ( !$fh = fopen( $filename, 'w' ) ) { 00247 throw new Exception( "couldn't open $filename for writing" ); 00248 } 00249 fwrite( $fh, $svg->asXML() ); 00250 if ( !fclose( $fh ) ) { 00251 throw new Exception( "couldn't close $filename" ); 00252 } 00253 } 00254 00261 public function writeImageWithApi( $spec, $format, $filename ) { 00262 // this is a hack because I can't get setImageOrientation() to work. See below. 00263 global $wgExiv2Command; 00264 00265 $image = new Imagick(); 00270 $orientation = self::$orientations[0]; // default is normal orientation 00271 if ( $format == 'jpg' ) { 00272 $orientation = self::$orientations[array_rand( self::$orientations )]; 00273 $spec = self::rotateImageSpec( $spec, $orientation['counterRotation'] ); 00274 } 00275 00276 $image->newImage( $spec['width'], $spec['height'], new ImagickPixel( $spec['fill'] ) ); 00277 00278 foreach ( $spec['draws'] as $drawSpec ) { 00279 $draw = new ImagickDraw(); 00280 $draw->setFillColor( $drawSpec['fill'] ); 00281 $draw->polygon( $drawSpec['shape'] ); 00282 $image->drawImage( $draw ); 00283 } 00284 00285 $image->setImageFormat( $format ); 00286 00287 // this doesn't work, even though it's documented to do so... 00288 // $image->setImageOrientation( $orientation['exifCode'] ); 00289 00290 $image->writeImage( $filename ); 00291 00292 // because the above setImageOrientation call doesn't work... nor can I get an external imagemagick binary to do this either... 00293 // hacking this for now (only works if you have exiv2 installed, a program to read and manipulate exif) 00294 if ( $wgExiv2Command ) { 00295 $cmd = wfEscapeShellArg( $wgExiv2Command ) 00296 . " -M " 00297 . wfEscapeShellArg( "set Exif.Image.Orientation " . $orientation['exifCode'] ) 00298 . " " 00299 . wfEscapeShellArg( $filename ); 00300 00301 $retval = 0; 00302 $err = wfShellExec( $cmd, $retval ); 00303 if ( $retval !== 0 ) { 00304 print "Error with $cmd: $retval, $err\n"; 00305 } 00306 } 00307 } 00308 00316 private static function rotateImageSpec( &$spec, $matrix ) { 00317 $tSpec = array(); 00318 $dims = self::matrixMultiply2x2( $matrix, $spec['width'], $spec['height'] ); 00319 $correctionX = 0; 00320 $correctionY = 0; 00321 if ( $dims['x'] < 0 ) { 00322 $correctionX = abs( $dims['x'] ); 00323 } 00324 if ( $dims['y'] < 0 ) { 00325 $correctionY = abs( $dims['y'] ); 00326 } 00327 $tSpec['width'] = abs( $dims['x'] ); 00328 $tSpec['height'] = abs( $dims['y'] ); 00329 $tSpec['fill'] = $spec['fill']; 00330 $tSpec['draws'] = array(); 00331 foreach ( $spec['draws'] as $draw ) { 00332 $tDraw = array( 00333 'fill' => $draw['fill'], 00334 'shape' => array() 00335 ); 00336 foreach ( $draw['shape'] as $point ) { 00337 $tPoint = self::matrixMultiply2x2( $matrix, $point['x'], $point['y'] ); 00338 $tPoint['x'] += $correctionX; 00339 $tPoint['y'] += $correctionY; 00340 $tDraw['shape'][] = $tPoint; 00341 } 00342 $tSpec['draws'][] = $tDraw; 00343 } 00344 00345 return $tSpec; 00346 } 00347 00355 private static function matrixMultiply2x2( $matrix, $x, $y ) { 00356 return array( 00357 'x' => $x * $matrix[0][0] + $y * $matrix[0][1], 00358 'y' => $x * $matrix[1][0] + $y * $matrix[1][1] 00359 ); 00360 } 00361 00378 public function writeImageWithCommandLine( $spec, $format, $filename ) { 00379 global $wgImageMagickConvertCommand; 00380 $args = array(); 00381 $args[] = "-size " . wfEscapeShellArg( $spec['width'] . 'x' . $spec['height'] ); 00382 $args[] = wfEscapeShellArg( "xc:" . $spec['fill'] ); 00383 foreach ( $spec['draws'] as $draw ) { 00384 $fill = $draw['fill']; 00385 $polygon = self::shapePointsToString( $draw['shape'] ); 00386 $drawCommand = "fill $fill polygon $polygon"; 00387 $args[] = '-draw ' . wfEscapeShellArg( $drawCommand ); 00388 } 00389 $args[] = wfEscapeShellArg( $filename ); 00390 00391 $command = wfEscapeShellArg( $wgImageMagickConvertCommand ) . " " . implode( " ", $args ); 00392 $retval = null; 00393 wfShellExec( $command, $retval ); 00394 00395 return ( $retval === 0 ); 00396 } 00397 00403 public function getRandomColor() { 00404 $components = array(); 00405 for ( $i = 0; $i <= 2; $i++ ) { 00406 $components[] = mt_rand( 0, 255 ); 00407 } 00408 00409 return 'rgb(' . join( ', ', $components ) . ')'; 00410 } 00411 00418 private function getRandomWordPairs( $number ) { 00419 $lines = $this->getRandomLines( $number * 2 ); 00420 // construct pairs of words 00421 $pairs = array(); 00422 $count = count( $lines ); 00423 for ( $i = 0; $i < $count; $i += 2 ) { 00424 $pairs[] = array( $lines[$i], $lines[$i + 1] ); 00425 } 00426 00427 return $pairs; 00428 } 00429 00440 private function getRandomLines( $number_desired ) { 00441 $filepath = $this->dictionaryFile; 00442 00443 // initialize array of lines 00444 $lines = array(); 00445 for ( $i = 0; $i < $number_desired; $i++ ) { 00446 $lines[] = null; 00447 } 00448 00449 /* 00450 * This algorithm obtains N random lines from a file in one single pass. It does this by replacing elements of 00451 * a fixed-size array of lines, less and less frequently as it reads the file. 00452 */ 00453 $fh = fopen( $filepath, "r" ); 00454 if ( !$fh ) { 00455 throw new Exception( "couldn't open $filepath" ); 00456 } 00457 $line_number = 0; 00458 $max_index = $number_desired - 1; 00459 while ( !feof( $fh ) ) { 00460 $line = fgets( $fh ); 00461 if ( $line !== false ) { 00462 $line_number++; 00463 $line = trim( $line ); 00464 if ( mt_rand( 0, $line_number ) <= $max_index ) { 00465 $lines[mt_rand( 0, $max_index )] = $line; 00466 } 00467 } 00468 } 00469 fclose( $fh ); 00470 if ( $line_number < $number_desired ) { 00471 throw new Exception( "not enough lines in $filepath" ); 00472 } 00473 00474 return $lines; 00475 } 00476 }