10 class BitmapHandler extends ImageHandler {
11 function normaliseParams( $image, &$params ) {
12 global $wgMaxImageArea;
13 if ( !parent::normaliseParams( $image, $params ) ) {
17 $mimeType = $image->getMimeType();
18 $srcWidth = $image->getWidth( $params['page'] );
19 $srcHeight = $image->getHeight( $params['page'] );
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 )
30 # Don't make an image bigger than the source
31 $params['physicalWidth'] = $params['width'];
32 $params['physicalHeight'] = $params['height'];
34 if ( $params['physicalWidth'] >= $srcWidth ) {
35 $params['physicalWidth'] = $srcWidth;
36 $params['physicalHeight'] = $srcHeight;
43 function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 ) {
44 global $wgUseImageMagick, $wgImageMagickConvertCommand, $wgImageMagickTempDir;
45 global $wgCustomConvertCommand;
46 global $wgSharpenParameter, $wgSharpenReductionThreshold;
47 global $wgMaxAnimatedGifArea;
49 if ( !$this->normaliseParams( $image, $params ) ) {
50 return new TransformParameterError( $params );
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();
61 wfDebug( __METHOD__.": creating {$physicalWidth}x{$physicalHeight} thumbnail at $dstPath\n" );
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 );
70 // No output path available, client side scaling only
72 } elseif ( $wgUseImageMagick ) {
74 } elseif ( $wgCustomConvertCommand ) {
76 } elseif ( function_exists( 'imagecreatetruecolor' ) ) {
81 wfDebug( __METHOD__.": scaler $scaler\n" );
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 );
89 if ( $flags & self::TRANSFORM_LATER ) {
90 wfDebug( __METHOD__.": Transforming later per flags.\n" );
91 return new ThumbnailImage( $image, $dstUrl, $clientWidth, $clientHeight, $dstPath );
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 );
99 if ( $scaler == 'im' ) {
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 );
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
120 // Coalesce is needed to scale animated GIFs properly (bug 1017).
121 $animation = ' -coalesce ';
125 if ( strval( $wgImageMagickTempDir ) !== '' ) {
126 $tempEnv = 'MAGICK_TMPDIR=' . wfEscapeShellArg( $wgImageMagickTempDir ) . ' ';
131 # Specify white background color, will be used for transparent images
132 # in Internet Explorer/Windows instead of default black.
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.
140 wfEscapeShellArg($wgImageMagickConvertCommand) .
141 " {$quality} -background white -size {$physicalWidth} ".
142 wfEscapeShellArg($srcPath . $frame) .
144 // For the -resize option a "!" is needed to force exact size,
145 // or ImageMagick may decide your ratio is wrong and slice off
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.
169 # First find out what kind of file this is, and select the correct
170 # input routine for this.
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' ),
179 if( !isset( $typemap[$mimeType] ) ) {
180 $err = 'Image type not supported';
182 return new MediaTransformError( 'thumbnail_error', $clientWidth, $clientHeight, $err );
184 list( $loader, $colorStyle, $saveType ) = $typemap[$mimeType];
186 if( !function_exists( $loader ) ) {
187 $err = "Incomplete GD library configuration: missing function $loader";
189 return new MediaTransformError( 'thumbnail_error', $clientWidth, $clientHeight, $err );
192 $src_image = call_user_func( $loader, $srcPath );
193 $dst_image = imagecreatetruecolor( $physicalWidth, $physicalHeight );
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 );
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,
206 $physicalWidth, $physicalHeight, imagesx( $src_image ), imagesy( $src_image ) );
208 imagecopyresampled( $dst_image, $src_image,
210 $physicalWidth, $physicalHeight, imagesx( $src_image ), imagesy( $src_image ) );
213 imagesavealpha( $dst_image, true );
215 call_user_func( $saveType, $dst_image, $dstPath );
216 imagedestroy( $dst_image );
217 imagedestroy( $src_image );
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 );
228 return new ThumbnailImage( $image, $dstUrl, $clientWidth, $clientHeight, $dstPath );
232 static function imageJpegWrapper( $dst_image, $thumbPath ) {
233 imageinterlace( $dst_image );
234 imagejpeg( $dst_image, $thumbPath, 95 );
238 function getMetadata( $image, $filename ) {
240 if( $wgShowEXIF && file_exists( $filename ) ) {
241 $exif = new Exif( $filename );
242 $data = $exif->getFilteredData();
244 $data['MEDIAWIKI_EXIF_VERSION'] = Exif::version();
245 return serialize( $data );
254 function getMetadataType( $image ) {
258 function isMetadataValid( $image, $metadata ) {
260 if ( !$wgShowEXIF ) {
261 # Metadata disabled and so an empty field is expected
264 if ( $metadata === '0' ) {
265 # Special value indicating that there is no EXIF data in the file
268 $exif = @unserialize( $metadata );
269 if ( !isset( $exif['MEDIAWIKI_EXIF_VERSION'] ) ||
270 $exif['MEDIAWIKI_EXIF_VERSION'] != Exif::version() )
273 wfDebug( __METHOD__.": wrong version\n" );
280 * Get a list of EXIF metadata items which should be displayed when
281 * the metadata table is collapsed.
283 * @return array of strings
286 function visibleMetadataFields() {
288 $lines = explode( "\n", wfMsgForContent( 'metadata-fields' ) );
289 foreach( $lines as $line ) {
291 if( preg_match( '/^\\*\s*(.*?)\s*$/', $line, $matches ) ) {
292 $fields[] = $matches[1];
295 $fields = array_map( 'strtolower', $fields );
299 function formatMetadata( $image ) {
301 'visible' => array(),
302 'collapsed' => array()
304 $metadata = $image->getMetadata();
308 $exif = unserialize( $metadata );
312 unset( $exif['MEDIAWIKI_EXIF_VERSION'] );
313 $format = new FormatExif( $exif );
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',