r85186 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r85185‎ | r85186 | r85187 >
Date:11:59, 2 April 2011
Author:btongminh
Status:deferred
Tags:
Comment:
VipsScaler: Add more user configuration (untested)
* Add sharpening with a convolution matrix
* Add shrinkFactor conditions
* Refactored the calling of vips into a VipsCommand class
* Made $wgVipsConditions into a subarray of $wgVipsOptions
Modified paths:
  • /trunk/extensions/VipsScaler/VipsScaler.php (modified) (history)
  • /trunk/extensions/VipsScaler/VipsScaler_body.php (modified) (history)

Diff [purge]

Index: trunk/extensions/VipsScaler/VipsScaler.php
@@ -35,4 +35,30 @@
3636
3737 # Download vips from http://www.vips.ecs.soton.ac.uk/
3838 $wgVipsCommand = 'vips';
39 -$wgVipsConditions = array();
 39+
 40+# Options and conditions for images to be scaled with this scaler
 41+$wgVipsOptions = array(
 42+ # Sharpen jpeg files which are shrunk more than 1.2
 43+ array(
 44+ 'conditions' => array(
 45+ 'mimeType' => 'image/jpeg',
 46+ 'maxShrinkFactor' => 1.2,
 47+ ),
 48+ 'sharpen' => true,
 49+ ),
 50+ # Other jpeg files
 51+ array(
 52+ 'conditions' => array(
 53+ 'mimeType' => 'image/jpeg',
 54+ ),
 55+ 'sharpen' => false,
 56+ 'bilinear' => true,
 57+ ),
 58+ # Do a simple shrink for PNGs
 59+ array(
 60+ 'conditions' => array(
 61+ 'mimeType' => 'image/png',
 62+ ),
 63+ ),
 64+);
 65+
Index: trunk/extensions/VipsScaler/VipsScaler_body.php
@@ -11,118 +11,165 @@
1212 */
1313 public static function onTransform( $handler, $file, &$params, &$mto ) {
1414 # Check $wgVipsConditions
15 - if ( !self::shouldHandle( $handler, $file, $params ) ) {
 15+ $options = self::getHandlerOptions( $handler, $file, $params );
 16+ if ( !$options ) {
1617 return true;
1718 }
1819
19 - # Get the proper im_XXX2vips handler
20 - $vipsHandler = self::getVipsHandler( $file );
21 - if ( !$vipsHandler ) {
 20+ $vipsCommands = self::makeCommands( $handler, $file, $params, $options );
 21+ if ( count( $vipsCommands ) == 0 ) {
2222 return true;
2323 }
2424
25 - # Get a temporary file with .v extension
26 - $tmp = self::makeTempV();
27 - # Convert the source image to a .v file
28 - list( $err, $retval ) = self::vips( $vipsHandler, $params['srcPath'], $tmp );
29 - if ( $retval != 0 ) {
30 - wfDebug( __METHOD__ . ": $vipsHandler failed!\n" );
31 - unlink( $tmp );
32 - $mto = $handler->getMediaTransformError( $params, $err );
33 - return false;
34 - }
35 -
36 - # Rotate if necessary
37 - $rotation = 360 - $handler->getRotation( $file );
38 - if ( $rotation % 360 != 0 && $rotation % 90 == 0 ) {
39 - $tmp2 = self::makeTempV();
 25+ # Execute the commands
 26+ foreach ( $vipsCommands as $i => $command ) {
 27+ # Set input/output files
 28+ if ( $i == 0 && count( $vipsCommands ) == 1 ) {
 29+ # Single command, so output directly to dstPath
 30+ $command->setIO( $params['srcPath'], $params['dstPath'] );
 31+ } elseif ( $i == 0 ) {
 32+ # First command, input from srcPath, output to temp
 33+ $command->setIO( $params['srcPath'], 'v', VipsCommand::TEMP_OUTPUT );
 34+ } elseif ( $i + 1 == count( $vipsCommands ) ) {
 35+ # Last command, output to dstPath
 36+ $command->setIO( $vipsCommands[$i - 1], $params['dstPath'] );
 37+ } else {
 38+ $command->setIO( $vipsCommands[$i - 1], 'v', VipsCommand::TEMP_OUTPUT );
 39+ }
4040
41 - list( $err, $retval ) = self::vips( "im_rot{$rotation}", $tmp, $tmp2 );
42 - unlink( $tmp );
 41+ $retval = $command->execute();
4342 if ( $retval != 0 ) {
44 - unlink( $tmp2 );
45 - $mto = $handler->getMediaTransformError( $params, $err );
 43+ wfDebug( __METHOD__ . ": vips command failed!\n" );
 44+ $mto = $handler->getMediaTransformError( $params, $command->getErrorString() );
4645 return false;
4746 }
48 -
49 - # Set the new rotated file
50 - $tmp = $tmp2;
5147 }
5248
53 - # Calculate shrink factors. Offsetting by 0.5 pixels is required
54 - # because of rounding down of the target size by VIPS. See 25990#c7
55 - if ( $rotation % 180 == 90 ) {
56 - # Rotated 90 degrees, so width = height and vice versa
57 - $rx = $params['srcWidth'] / ($params['physicalHeight'] + 0.5);
58 - $ry = $params['srcHeight'] / ($params['physicalWidth'] + 0.5);
59 - } else {
60 - $rx = $params['srcWidth'] / ($params['physicalWidth'] + 0.5);
61 - $ry = $params['srcHeight'] / ($params['physicalHeight'] + 0.5);
62 - }
63 -
64 - # Scale the image to the final output
65 - list( $err, $retval ) = self::vips( 'im_shrink', $tmp,
66 - $params['dstPath'], $rx, $ry );
67 - # Remove the temp file
68 - unlink( $tmp );
69 - if ( $retval != 0 ) {
70 - $mto = $handler->getMediaTransformError( $params, $err );
71 - return false;
72 - }
73 -
7449 # Set the output variable
7550 $mto = new ThumbnailImage( $file, $params['dstUrl'],
7651 $params['clientWidth'], $params['clientHeight'], $params['dstPath'] );
7752
7853 # Stop processing
7954 return false;
80 -
8155 }
8256
83 - /**
84 - * Call the vips binary with varargs and returns the error output and
85 - * return value.
86 - *
87 - * @param varargs
88 - * @return array
89 - */
90 - protected static function vips( /* varargs */ ) {
 57+ public static function makeCommands( $handler, $file, $params, $options ) {
9158 global $wgVipsCommand;
 59+ $commands = array();
9260
93 - $args = func_get_args();
94 - $cmd = wfEscapeShellArg( $wgVipsCommand );
95 - foreach ( $args as $arg ) {
96 - $cmd .= ' ' . wfEscapeShellArg( $arg );
 61+ # Get the proper im_XXX2vips handler
 62+ $vipsHandler = self::getVipsHandler( $file );
 63+ if ( !$vipsHandler ) {
 64+ return array();
9765 }
9866
99 - $retval = 0;
100 - $err = wfShellExec( $cmd, $retval );
101 - return array( $err, $retval );
 67+ # Check if we need to convert to a .v file first
 68+ if ( !empty( $options['preconvert'] ) ) {
 69+ $commands[] = new VipsCommand( $wgVipsCommand, array( $vipsHandler ) );
 70+ }
 71+
 72+ # Do the resizing
 73+ $rotation = 360 - $handler->getRotation( $file );
 74+
 75+ if ( empty( $options['bilinear'] ) ) {
 76+ # Calculate shrink factors. Offsetting by 0.5 pixels is required
 77+ # because of rounding down of the target size by VIPS. See 25990#c7
 78+ if ( $rotation % 180 == 90 ) {
 79+ # Rotated 90 degrees, so width = height and vice versa
 80+ $rx = $params['srcWidth'] / ($params['physicalHeight'] + 0.5);
 81+ $ry = $params['srcHeight'] / ($params['physicalWidth'] + 0.5);
 82+ } else {
 83+ $rx = $params['srcWidth'] / ($params['physicalWidth'] + 0.5);
 84+ $ry = $params['srcHeight'] / ($params['physicalHeight'] + 0.5);
 85+ }
 86+
 87+ $commands[] = new VipsCommand( $wgVipsCommand, array( 'im_shrink', $rx, $ry ) );
 88+ } else {
 89+ if ( $rotation % 180 == 90 ) {
 90+ $dstWidth = $params['physicalHeight'];
 91+ $dstHeight = $params['physicalWidth'];
 92+ } else {
 93+ $dstWidth = $params['physicalWidth'];
 94+ $dstHeight = $params['physicalHeight'];
 95+ }
 96+ $commands[] = new VipsCommand( $wgVipsCommand,
 97+ array( 'im_resize_linear', $dstWidth, $dstHeight ) );
 98+ }
 99+
 100+ if ( !empty( $options['sharpen'] ) ) {
 101+ if ( !is_array( $options['sharpen'] ) ) {
 102+ # Use a default convolution matrix
 103+ # See http://homepages.inf.ed.ac.uk/rbf/HIPR2/unsharp.htm for
 104+ # an explanation on how to use the Laplacian operator to
 105+ # sharpen an image using a convolution matrix
 106+ $convolution = array(
 107+ # Matrix descriptor
 108+ array( 3, 3, 1, 0 ),
 109+ # Matrix itself
 110+ array( -1, -2, -1 ),
 111+ array( -2, 8, -2 ),
 112+ array( -1, -2, -1 )
 113+ );
 114+ } else {
 115+ $convolution = $options['sharpen'];
 116+ }
 117+
 118+ $commands[] = new VipsConvolution( $wgVipsCommand,
 119+ array( 'im_conv', $convolution ) );
 120+ }
 121+
 122+ # Rotation
 123+ if ( $rotation % 360 != 0 && $rotation % 90 == 0 ) {
 124+ $commands[] = new VipsComand( $wgVipsCommand, array( "im_rot{$rotation}" ) );
 125+ }
 126+
 127+ return $commands;
102128 }
103129
 130+
104131 /**
105 - * Check the file and params against $wgVipsConditions
 132+ * Check the file and params against $wgVipsOptions
106133 *
107134 * @param BitmapHandler $handler
108135 * @param File $file
109136 * @param array $params
110137 * @return bool
111138 */
112 - protected static function shouldHandle( $handler, $file, $params ) {
113 - global $wgVipsConditions;
 139+ protected static function getHandlerOptions( $handler, $file, $params ) {
 140+ global $wgVipsOptions;
114141 # Iterate over conditions
115 - foreach ( $wgVipsConditions as $condition ) {
 142+ foreach ( $wgVipsOptions as $option ) {
 143+ if ( isset( $option['conditions'] ) ) {
 144+ $condition = $option['conditions'];
 145+ } else {
 146+ # Unconditionally pass
 147+ return $option;
 148+ }
 149+
116150 if ( isset( $condition['mimeType'] ) &&
117151 $file->getMimeType() != $condition['mimeType'] ) {
118152 continue;
119153 }
 154+
120155 $area = $handler->getImageArea( $file, $params['srcWidth'], $params['srcHeight'] );
121156 if ( isset( $condition['minArea'] ) && $area < $condition['minArea'] ) {
122157 continue;
123158 }
124 - if ( isset( $condition['maxArea'] ) && $area > $condition['maxArea'] ) {
 159+ if ( isset( $condition['maxArea'] ) && $area >= $condition['maxArea'] ) {
125160 continue;
126161 }
 162+
 163+ $shrinkFactor = $params['srcWidth'] / (
 164+ ( ( $handler->getRotation( $file ) % 180 ) == 90 ) ?
 165+ $params['dstHeight'], $params['dstWidth'] );
 166+ if ( isset( $condition['minShrinkFactor'] ) &&
 167+ $shrinkFactor < $condition['minShrinkFactor'] ) {
 168+ continue;
 169+ }
 170+ if ( isset( $condition['maxShrinkFactor'] ) &&
 171+ $shrinkFactor >= $condition['maxShrinkFactor'] ) {
 172+ continue;
 173+ }
127174
128175 # This condition passed
129176 return true;
@@ -146,20 +193,132 @@
147194 }
148195 }
149196
 197+
 198+}
 199+
 200+/**
 201+ * Wrapper class around the vips command, useful to chain multiple commands
 202+ * with intermediate .v files
 203+ */
 204+class VipsCommand {
 205+ /** Flag to indicate that the output file should be a temporary .v file */
 206+ public const TEMP_OUTPUT = true;
 207+ /**
 208+ * Constructor
 209+ *
 210+ * @param string $vips Path to binary
 211+ * @param array $args Array or arguments
 212+ */
 213+ public function __construct( $vips, $args ) {
 214+ $this->vips = $vips;
 215+ $this->args = $args;
 216+ }
150217 /**
151 - * Generate a random, non-existent temporary file with a .v extension.
 218+ * Set the input and output file of this command
152219 *
 220+ * @param mixed $input Input file name or an VipsCommand object to use the
 221+ * output of that command
 222+ * @param string $output Output file name or extension of the temporary file
 223+ * @param bool $tempOutput Output to a temporary file
 224+ */
 225+ public function setIO( $input, $output, $tempOutput = false ) {
 226+ if ( $input instanceof VipsCommand ) {
 227+ $this->input = $input->getOutput();
 228+ $this->removeInput = true;
 229+ } else {
 230+ $this->input = $input;
 231+ $this->removeInput = false;
 232+ }
 233+ if ( $tempOutput ) {
 234+ $this->output = self::makeTemp( $output );
 235+ } else {
 236+ $this->output = $output;
 237+ }
 238+ }
 239+ /**
 240+ * Returns the output filename
153241 * @return string
154242 */
155 - protected static function makeTempV() {
 243+ public function getOutput() {
 244+ return $this->output;
 245+ }
 246+ /**
 247+ * Return the output of the command
 248+ * @return string
 249+ */
 250+ public function getErrorString() {
 251+ return $this->err;
 252+ }
 253+
 254+ /**
 255+ * Call the vips binary with varargs and returns the return value.
 256+ *
 257+ * @return int Return value
 258+ */
 259+ public function execute() {
 260+ # Build and escape the command string
 261+ $cmd = wfEscapeShellArg( $this->vips );
 262+ foreach ( $this->args as $arg ) {
 263+ $cmd .= ' ' . wfEscapeShellArg( $arg );
 264+ }
 265+
 266+ # Execute
 267+ $retval = 0;
 268+ $this->err = wfShellExec( $cmd, $retval );
 269+
 270+ # Cleanup temp file
 271+ if ( $this->removeInput ) {
 272+ unlink( $this->input );
 273+ }
 274+
 275+ return $retval;
 276+ }
 277+
 278+ /**
 279+ * Generate a random, non-existent temporary file with a specified
 280+ * extension.
 281+ *
 282+ * @param string Extension
 283+ * @return string
 284+ */
 285+ protected static function makeTemp( $extension ) {
156286 do {
157287 # Generate a random file
158288 $fileName = wfTempDir() . DIRECTORY_SEPARATOR .
159 - dechex( mt_rand() ) . dechex( mt_rand() ) . '.v';
 289+ dechex( mt_rand() ) . dechex( mt_rand() ) .
 290+ '.' . $extension;
160291 } while ( file_exists( $fileName ) );
161292 # Create the file
162293 touch( $fileName );
163294
164295 return $fileName;
165296 }
 297+
 298+}
 299+
 300+/**
 301+ * A wrapper class around im_conv because that command expects a a convolution
 302+ * matrix file as its third argument
 303+ */
 304+class VipsConvolution extends VipsCommand {
 305+ public function execute() {
 306+ # Convert a 2D array into a space/newline separated matrix
 307+ $convolutionMatrix = array_shift( $this->args );
 308+ $convolutionString = '';
 309+ foreach ( $convolutionMatrix as $row ) {
 310+ $convolutionString .= implode( ' ', $row ) . "\n";
 311+ }
 312+ # Save the matrix in a tempfile
 313+ $convolutionFile = self::makeTemp( 'conv' );
 314+ file_put_contents( $convolutionFile, $convolutionString );
 315+ array_unshift( $this->args, $convolutionFile );
 316+
 317+ # Call the parent to actually execute the command
 318+ $retval = parent::execute();
 319+
 320+ # Remove the temporary matrix file
 321+ unlink( $convolutionFile );
 322+
 323+ return $retval;
 324+ }
166325 }
\ No newline at end of file

Status & tagging log