]> scripts.mit.edu Git - autoinstalls/mediawiki.git/blob - includes/media/Bitmap.php
MediaWiki 1.15.4-scripts
[autoinstalls/mediawiki.git] / includes / media / Bitmap.php
1 <?php
2 /**
3  * @file
4  * @ingroup Media
5  */
6
7 /**
8  * @ingroup Media
9  */
10 class BitmapHandler extends ImageHandler {
11         function normaliseParams( $image, &$params ) {
12                 global $wgMaxImageArea;
13                 if ( !parent::normaliseParams( $image, $params ) ) {
14                         return false;
15                 }
16
17                 $mimeType = $image->getMimeType();
18                 $srcWidth = $image->getWidth( $params['page'] );
19                 $srcHeight = $image->getHeight( $params['page'] );
20
21                 # Don't thumbnail an image so big that it will fill hard drives and send servers into swap
22                 # JPEG has the handy property of allowing thumbnailing without full decompression, so we make
23                 # an exception for it.
24                 if ( $mimeType !== 'image/jpeg' &&
25                         $srcWidth * $srcHeight > $wgMaxImageArea )
26                 {
27                         return false;
28                 }
29
30                 # Don't make an image bigger than the source
31                 $params['physicalWidth'] = $params['width'];
32                 $params['physicalHeight'] = $params['height'];
33
34                 if ( $params['physicalWidth'] >= $srcWidth ) {
35                         $params['physicalWidth'] = $srcWidth;
36                         $params['physicalHeight'] = $srcHeight;
37                         return true;
38                 }
39
40                 return true;
41         }
42
43         function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 ) {
44                 global $wgUseImageMagick, $wgImageMagickConvertCommand, $wgImageMagickTempDir;
45                 global $wgCustomConvertCommand, $wgUseImageResize;
46                 global $wgSharpenParameter, $wgSharpenReductionThreshold;
47                 global $wgMaxAnimatedGifArea;
48
49                 if ( !$this->normaliseParams( $image, $params ) ) {
50                         return new TransformParameterError( $params );
51                 }
52                 $physicalWidth = $params['physicalWidth'];
53                 $physicalHeight = $params['physicalHeight'];
54                 $clientWidth = $params['width'];
55                 $clientHeight = $params['height'];
56                 $srcWidth = $image->getWidth();
57                 $srcHeight = $image->getHeight();
58                 $mimeType = $image->getMimeType();
59                 $srcPath = $image->getPath();
60                 $retval = 0;
61                 wfDebug( __METHOD__.": creating {$physicalWidth}x{$physicalHeight} thumbnail at $dstPath\n" );
62
63                 if ( !$image->mustRender() && $physicalWidth == $srcWidth && $physicalHeight == $srcHeight ) {
64                         # normaliseParams (or the user) wants us to return the unscaled image
65                         wfDebug( __METHOD__.": returning unscaled image\n" );
66                         return new ThumbnailImage( $image, $image->getURL(), $clientWidth, $clientHeight, $srcPath );
67                 }
68
69                 if ( !$dstPath ) {
70                         // No output path available, client side scaling only
71                         $scaler = 'client';
72                 } elseif( !$wgUseImageResize ) {
73                         $scaler = 'client';
74                 } elseif ( $wgUseImageMagick ) {
75                         $scaler = 'im';
76                 } elseif ( $wgCustomConvertCommand ) {
77                         $scaler = 'custom';
78                 } elseif ( function_exists( 'imagecreatetruecolor' ) ) {
79                         $scaler = 'gd';
80                 } else {
81                         $scaler = 'client';
82                 }
83                 wfDebug( __METHOD__.": scaler $scaler\n" );
84
85                 if ( $scaler == 'client' ) {
86                         # Client-side image scaling, use the source URL
87                         # Using the destination URL in a TRANSFORM_LATER request would be incorrect
88                         return new ThumbnailImage( $image, $image->getURL(), $clientWidth, $clientHeight, $srcPath );
89                 }
90
91                 if ( $flags & self::TRANSFORM_LATER ) {
92                         wfDebug( __METHOD__.": Transforming later per flags.\n" );
93                         return new ThumbnailImage( $image, $dstUrl, $clientWidth, $clientHeight, $dstPath );
94                 }
95
96                 if ( !wfMkdirParents( dirname( $dstPath ) ) ) {
97                         wfDebug( __METHOD__.": Unable to create thumbnail destination directory, falling back to client scaling\n" );
98                         return new ThumbnailImage( $image, $image->getURL(), $clientWidth, $clientHeight, $srcPath );
99                 }
100
101                 if ( $scaler == 'im' ) {
102                         # use ImageMagick
103
104                         $quality = '';
105                         $sharpen = '';
106                         $frame = '';
107                         $animation = '';
108                         if ( $mimeType == 'image/jpeg' ) {
109                                 $quality = "-quality 80"; // 80%
110                                 # Sharpening, see bug 6193
111                                 if ( ( $physicalWidth + $physicalHeight ) / ( $srcWidth + $srcHeight ) < $wgSharpenReductionThreshold ) {
112                                         $sharpen = "-sharpen " . wfEscapeShellArg( $wgSharpenParameter );
113                                 }
114                         } elseif ( $mimeType == 'image/png' ) {
115                                 $quality = "-quality 95"; // zlib 9, adaptive filtering
116                         } elseif( $mimeType == 'image/gif' ) {
117                                 if( $srcWidth * $srcHeight > $wgMaxAnimatedGifArea ) {
118                                         // Extract initial frame only; we're so big it'll
119                                         // be a total drag. :P
120                                         $frame = '[0]';
121                                 } else {
122                                         // Coalesce is needed to scale animated GIFs properly (bug 1017).
123                                         $animation = ' -coalesce ';
124                                 }
125                         }
126
127                         if ( strval( $wgImageMagickTempDir ) !== '' ) {
128                                 $tempEnv = 'MAGICK_TMPDIR=' . wfEscapeShellArg( $wgImageMagickTempDir ) . ' ';
129                         } else {
130                                 $tempEnv = '';
131                         }
132
133                         # Specify white background color, will be used for transparent images
134                         # in Internet Explorer/Windows instead of default black.
135
136                         # Note, we specify "-size {$physicalWidth}" and NOT "-size {$physicalWidth}x{$physicalHeight}".
137                         # It seems that ImageMagick has a bug wherein it produces thumbnails of
138                         # the wrong size in the second case.
139
140                         $cmd  = 
141                                 $tempEnv .
142                                 wfEscapeShellArg($wgImageMagickConvertCommand) .
143                                 " {$quality} -background white -size {$physicalWidth} ".
144                                 wfEscapeShellArg($srcPath . $frame) .
145                                 $animation .
146                                 // For the -resize option a "!" is needed to force exact size,
147                                 // or ImageMagick may decide your ratio is wrong and slice off
148                                 // a pixel.
149                                 " -thumbnail " . wfEscapeShellArg( "{$physicalWidth}x{$physicalHeight}!" ) .
150                                 " -depth 8 $sharpen " .
151                                 wfEscapeShellArg($dstPath) . " 2>&1";
152                         wfDebug( __METHOD__.": running ImageMagick: $cmd\n");
153                         wfProfileIn( 'convert' );
154                         $err = wfShellExec( $cmd, $retval );
155                         wfProfileOut( 'convert' );
156                 } elseif( $scaler == 'custom' ) {
157                         # Use a custom convert command
158                         # Variables: %s %d %w %h
159                         $src = wfEscapeShellArg( $srcPath );
160                         $dst = wfEscapeShellArg( $dstPath );
161                         $cmd = $wgCustomConvertCommand;
162                         $cmd = str_replace( '%s', $src, str_replace( '%d', $dst, $cmd ) ); # Filenames
163                         $cmd = str_replace( '%h', $physicalHeight, str_replace( '%w', $physicalWidth, $cmd ) ); # Size
164                         wfDebug( __METHOD__.": Running custom convert command $cmd\n" );
165                         wfProfileIn( 'convert' );
166                         $err = wfShellExec( $cmd, $retval );
167                         wfProfileOut( 'convert' );
168                 } else /* $scaler == 'gd' */ {
169                         # Use PHP's builtin GD library functions.
170                         #
171                         # First find out what kind of file this is, and select the correct
172                         # input routine for this.
173
174                         $typemap = array(
175                                 'image/gif'          => array( 'imagecreatefromgif',  'palette',   'imagegif'  ),
176                                 'image/jpeg'         => array( 'imagecreatefromjpeg', 'truecolor', array( __CLASS__, 'imageJpegWrapper' ) ),
177                                 'image/png'          => array( 'imagecreatefrompng',  'bits',      'imagepng'  ),
178                                 'image/vnd.wap.wbmp' => array( 'imagecreatefromwbmp', 'palette',   'imagewbmp'  ),
179                                 'image/xbm'          => array( 'imagecreatefromxbm',  'palette',   'imagexbm'  ),
180                         );
181                         if( !isset( $typemap[$mimeType] ) ) {
182                                 $err = 'Image type not supported';
183                                 wfDebug( "$err\n" );
184                                 return new MediaTransformError( 'thumbnail_error', $clientWidth, $clientHeight, $err );
185                         }
186                         list( $loader, $colorStyle, $saveType ) = $typemap[$mimeType];
187
188                         if( !function_exists( $loader ) ) {
189                                 $err = "Incomplete GD library configuration: missing function $loader";
190                                 wfDebug( "$err\n" );
191                                 return new MediaTransformError( 'thumbnail_error', $clientWidth, $clientHeight, $err );
192                         }
193
194                         $src_image = call_user_func( $loader, $srcPath );
195                         $dst_image = imagecreatetruecolor( $physicalWidth, $physicalHeight );
196
197                         // Initialise the destination image to transparent instead of
198                         // the default solid black, to support PNG and GIF transparency nicely
199                         $background = imagecolorallocate( $dst_image, 0, 0, 0 );
200                         imagecolortransparent( $dst_image, $background );
201                         imagealphablending( $dst_image, false );
202
203                         if( $colorStyle == 'palette' ) {
204                                 // Don't resample for paletted GIF images.
205                                 // It may just uglify them, and completely breaks transparency.
206                                 imagecopyresized( $dst_image, $src_image,
207                                         0,0,0,0,
208                                         $physicalWidth, $physicalHeight, imagesx( $src_image ), imagesy( $src_image ) );
209                         } else {
210                                 imagecopyresampled( $dst_image, $src_image,
211                                         0,0,0,0,
212                                         $physicalWidth, $physicalHeight, imagesx( $src_image ), imagesy( $src_image ) );
213                         }
214
215                         imagesavealpha( $dst_image, true );
216
217                         call_user_func( $saveType, $dst_image, $dstPath );
218                         imagedestroy( $dst_image );
219                         imagedestroy( $src_image );
220                         $retval = 0;
221                 }
222
223                 $removed = $this->removeBadFile( $dstPath, $retval );
224                 if ( $retval != 0 || $removed ) {
225                         wfDebugLog( 'thumbnail',
226                                 sprintf( 'thumbnail failed on %s: error %d "%s" from "%s"',
227                                         wfHostname(), $retval, trim($err), $cmd ) );
228                         return new MediaTransformError( 'thumbnail_error', $clientWidth, $clientHeight, $err );
229                 } else {
230                         return new ThumbnailImage( $image, $dstUrl, $clientWidth, $clientHeight, $dstPath );
231                 }
232         }
233
234         static function imageJpegWrapper( $dst_image, $thumbPath ) {
235                 imageinterlace( $dst_image );
236                 imagejpeg( $dst_image, $thumbPath, 95 );
237         }
238
239
240         function getMetadata( $image, $filename ) {
241                 global $wgShowEXIF;
242                 if( $wgShowEXIF && file_exists( $filename ) ) {
243                         $exif = new Exif( $filename );
244                         $data = $exif->getFilteredData();
245                         if ( $data ) {
246                                 $data['MEDIAWIKI_EXIF_VERSION'] = Exif::version();
247                                 return serialize( $data );
248                         } else {
249                                 return '0';
250                         }
251                 } else {
252                         return '';
253                 }
254         }
255
256         function getMetadataType( $image ) {
257                 return 'exif';
258         }
259
260         function isMetadataValid( $image, $metadata ) {
261                 global $wgShowEXIF;
262                 if ( !$wgShowEXIF ) {
263                         # Metadata disabled and so an empty field is expected
264                         return true;
265                 }
266                 if ( $metadata === '0' ) {
267                         # Special value indicating that there is no EXIF data in the file
268                         return true;
269                 }
270                 $exif = @unserialize( $metadata );
271                 if ( !isset( $exif['MEDIAWIKI_EXIF_VERSION'] ) ||
272                         $exif['MEDIAWIKI_EXIF_VERSION'] != Exif::version() )
273                 {
274                         # Wrong version
275                         wfDebug( __METHOD__.": wrong version\n" );
276                         return false;
277                 }
278                 return true;
279         }
280
281         /**
282          * Get a list of EXIF metadata items which should be displayed when
283          * the metadata table is collapsed.
284          *
285          * @return array of strings
286          * @access private
287          */
288         function visibleMetadataFields() {
289                 $fields = array();
290                 $lines = explode( "\n", wfMsgForContent( 'metadata-fields' ) );
291                 foreach( $lines as $line ) {
292                         $matches = array();
293                         if( preg_match( '/^\\*\s*(.*?)\s*$/', $line, $matches ) ) {
294                                 $fields[] = $matches[1];
295                         }
296                 }
297                 $fields = array_map( 'strtolower', $fields );
298                 return $fields;
299         }
300
301         function formatMetadata( $image ) {
302                 $result = array(
303                         'visible' => array(),
304                         'collapsed' => array()
305                 );
306                 $metadata = $image->getMetadata();
307                 if ( !$metadata ) {
308                         return false;
309                 }
310                 $exif = unserialize( $metadata );
311                 if ( !$exif ) {
312                         return false;
313                 }
314                 unset( $exif['MEDIAWIKI_EXIF_VERSION'] );
315                 $format = new FormatExif( $exif );
316
317                 $formatted = $format->getFormattedData();
318                 // Sort fields into visible and collapsed
319                 $visibleFields = $this->visibleMetadataFields();
320                 foreach ( $formatted as $name => $value ) {
321                         $tag = strtolower( $name );
322                         self::addMeta( $result,
323                                 in_array( $tag, $visibleFields ) ? 'visible' : 'collapsed',
324                                 'exif',
325                                 $tag,
326                                 $value
327                         );
328                 }
329                 return $result;
330         }
331 }