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