00001 <?php
00010 class BitmapHandler extends ImageHandler {
00011 function normaliseParams( $image, &$params ) {
00012 global $wgMaxImageArea;
00013 if ( !parent::normaliseParams( $image, $params ) ) {
00014 return false;
00015 }
00016
00017 $mimeType = $image->getMimeType();
00018 $srcWidth = $image->getWidth( $params['page'] );
00019 $srcHeight = $image->getHeight( $params['page'] );
00020
00021 # Don't thumbnail an image so big that it will fill hard drives and send servers into swap
00022 # JPEG has the handy property of allowing thumbnailing without full decompression, so we make
00023 # an exception for it.
00024 if ( $mimeType !== 'image/jpeg' &&
00025 $srcWidth * $srcHeight > $wgMaxImageArea )
00026 {
00027 return false;
00028 }
00029
00030 # Don't make an image bigger than the source
00031 $params['physicalWidth'] = $params['width'];
00032 $params['physicalHeight'] = $params['height'];
00033
00034 if ( $params['physicalWidth'] >= $srcWidth ) {
00035 $params['physicalWidth'] = $srcWidth;
00036 $params['physicalHeight'] = $srcHeight;
00037 return true;
00038 }
00039
00040 return true;
00041 }
00042
00043 function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 ) {
00044 global $wgUseImageMagick, $wgImageMagickConvertCommand, $wgImageMagickTempDir;
00045 global $wgCustomConvertCommand, $wgUseImageResize;
00046 global $wgSharpenParameter, $wgSharpenReductionThreshold;
00047 global $wgMaxAnimatedGifArea;
00048
00049 if ( !$this->normaliseParams( $image, $params ) ) {
00050 return new TransformParameterError( $params );
00051 }
00052 $physicalWidth = $params['physicalWidth'];
00053 $physicalHeight = $params['physicalHeight'];
00054 $clientWidth = $params['width'];
00055 $clientHeight = $params['height'];
00056 $srcWidth = $image->getWidth();
00057 $srcHeight = $image->getHeight();
00058 $mimeType = $image->getMimeType();
00059 $srcPath = $image->getPath();
00060 $retval = 0;
00061 wfDebug( __METHOD__.": creating {$physicalWidth}x{$physicalHeight} thumbnail at $dstPath\n" );
00062
00063 if ( !$image->mustRender() && $physicalWidth == $srcWidth && $physicalHeight == $srcHeight ) {
00064 # normaliseParams (or the user) wants us to return the unscaled image
00065 wfDebug( __METHOD__.": returning unscaled image\n" );
00066 return new ThumbnailImage( $image, $image->getURL(), $clientWidth, $clientHeight, $srcPath );
00067 }
00068
00069 if ( !$dstPath ) {
00070
00071 $scaler = 'client';
00072 } elseif( !$wgUseImageResize ) {
00073 $scaler = 'client';
00074 } elseif ( $wgUseImageMagick ) {
00075 $scaler = 'im';
00076 } elseif ( $wgCustomConvertCommand ) {
00077 $scaler = 'custom';
00078 } elseif ( function_exists( 'imagecreatetruecolor' ) ) {
00079 $scaler = 'gd';
00080 } else {
00081 $scaler = 'client';
00082 }
00083 wfDebug( __METHOD__.": scaler $scaler\n" );
00084
00085 if ( $scaler == 'client' ) {
00086 # Client-side image scaling, use the source URL
00087 # Using the destination URL in a TRANSFORM_LATER request would be incorrect
00088 return new ThumbnailImage( $image, $image->getURL(), $clientWidth, $clientHeight, $srcPath );
00089 }
00090
00091 if ( $flags & self::TRANSFORM_LATER ) {
00092 wfDebug( __METHOD__.": Transforming later per flags.\n" );
00093 return new ThumbnailImage( $image, $dstUrl, $clientWidth, $clientHeight, $dstPath );
00094 }
00095
00096 if ( !wfMkdirParents( dirname( $dstPath ) ) ) {
00097 wfDebug( __METHOD__.": Unable to create thumbnail destination directory, falling back to client scaling\n" );
00098 return new ThumbnailImage( $image, $image->getURL(), $clientWidth, $clientHeight, $srcPath );
00099 }
00100
00101 if ( $scaler == 'im' ) {
00102 # use ImageMagick
00103
00104 $quality = '';
00105 $sharpen = '';
00106 $frame = '';
00107 $animation = '';
00108 if ( $mimeType == 'image/jpeg' ) {
00109 $quality = "-quality 80";
00110 # Sharpening, see bug 6193
00111 if ( ( $physicalWidth + $physicalHeight ) / ( $srcWidth + $srcHeight ) < $wgSharpenReductionThreshold ) {
00112 $sharpen = "-sharpen " . wfEscapeShellArg( $wgSharpenParameter );
00113 }
00114 } elseif ( $mimeType == 'image/png' ) {
00115 $quality = "-quality 95";
00116 } elseif( $mimeType == 'image/gif' ) {
00117 if( $srcWidth * $srcHeight > $wgMaxAnimatedGifArea ) {
00118
00119
00120 $frame = '[0]';
00121 } else {
00122
00123 $animation = ' -coalesce ';
00124 }
00125 }
00126
00127 if ( strval( $wgImageMagickTempDir ) !== '' ) {
00128 $tempEnv = 'MAGICK_TMPDIR=' . wfEscapeShellArg( $wgImageMagickTempDir ) . ' ';
00129 } else {
00130 $tempEnv = '';
00131 }
00132
00133 # Specify white background color, will be used for transparent images
00134 # in Internet Explorer/Windows instead of default black.
00135
00136 # Note, we specify "-size {$physicalWidth}" and NOT "-size {$physicalWidth}x{$physicalHeight}".
00137 # It seems that ImageMagick has a bug wherein it produces thumbnails of
00138 # the wrong size in the second case.
00139
00140 $cmd =
00141 $tempEnv .
00142 wfEscapeShellArg($wgImageMagickConvertCommand) .
00143 " {$quality} -background white -size {$physicalWidth} ".
00144 wfEscapeShellArg($srcPath . $frame) .
00145 $animation .
00146
00147
00148
00149 " -thumbnail " . wfEscapeShellArg( "{$physicalWidth}x{$physicalHeight}!" ) .
00150 " -depth 8 $sharpen " .
00151 wfEscapeShellArg($dstPath) . " 2>&1";
00152 wfDebug( __METHOD__.": running ImageMagick: $cmd\n");
00153 wfProfileIn( 'convert' );
00154 $err = wfShellExec( $cmd, $retval );
00155 wfProfileOut( 'convert' );
00156 } elseif( $scaler == 'custom' ) {
00157 # Use a custom convert command
00158 # Variables: %s %d %w %h
00159 $src = wfEscapeShellArg( $srcPath );
00160 $dst = wfEscapeShellArg( $dstPath );
00161 $cmd = $wgCustomConvertCommand;
00162 $cmd = str_replace( '%s', $src, str_replace( '%d', $dst, $cmd ) ); # Filenames
00163 $cmd = str_replace( '%h', $physicalHeight, str_replace( '%w', $physicalWidth, $cmd ) ); # Size
00164 wfDebug( __METHOD__.": Running custom convert command $cmd\n" );
00165 wfProfileIn( 'convert' );
00166 $err = wfShellExec( $cmd, $retval );
00167 wfProfileOut( 'convert' );
00168 } else {
00169 # Use PHP's builtin GD library functions.
00170 #
00171 # First find out what kind of file this is, and select the correct
00172 # input routine for this.
00173
00174 $typemap = array(
00175 'image/gif' => array( 'imagecreatefromgif', 'palette', 'imagegif' ),
00176 'image/jpeg' => array( 'imagecreatefromjpeg', 'truecolor', array( __CLASS__, 'imageJpegWrapper' ) ),
00177 'image/png' => array( 'imagecreatefrompng', 'bits', 'imagepng' ),
00178 'image/vnd.wap.wbmp' => array( 'imagecreatefromwbmp', 'palette', 'imagewbmp' ),
00179 'image/xbm' => array( 'imagecreatefromxbm', 'palette', 'imagexbm' ),
00180 );
00181 if( !isset( $typemap[$mimeType] ) ) {
00182 $err = 'Image type not supported';
00183 wfDebug( "$err\n" );
00184 return new MediaTransformError( 'thumbnail_error', $clientWidth, $clientHeight, $err );
00185 }
00186 list( $loader, $colorStyle, $saveType ) = $typemap[$mimeType];
00187
00188 if( !function_exists( $loader ) ) {
00189 $err = "Incomplete GD library configuration: missing function $loader";
00190 wfDebug( "$err\n" );
00191 return new MediaTransformError( 'thumbnail_error', $clientWidth, $clientHeight, $err );
00192 }
00193
00194 $src_image = call_user_func( $loader, $srcPath );
00195 $dst_image = imagecreatetruecolor( $physicalWidth, $physicalHeight );
00196
00197
00198
00199 $background = imagecolorallocate( $dst_image, 0, 0, 0 );
00200 imagecolortransparent( $dst_image, $background );
00201 imagealphablending( $dst_image, false );
00202
00203 if( $colorStyle == 'palette' ) {
00204
00205
00206 imagecopyresized( $dst_image, $src_image,
00207 0,0,0,0,
00208 $physicalWidth, $physicalHeight, imagesx( $src_image ), imagesy( $src_image ) );
00209 } else {
00210 imagecopyresampled( $dst_image, $src_image,
00211 0,0,0,0,
00212 $physicalWidth, $physicalHeight, imagesx( $src_image ), imagesy( $src_image ) );
00213 }
00214
00215 imagesavealpha( $dst_image, true );
00216
00217 call_user_func( $saveType, $dst_image, $dstPath );
00218 imagedestroy( $dst_image );
00219 imagedestroy( $src_image );
00220 $retval = 0;
00221 }
00222
00223 $removed = $this->removeBadFile( $dstPath, $retval );
00224 if ( $retval != 0 || $removed ) {
00225 wfDebugLog( 'thumbnail',
00226 sprintf( 'thumbnail failed on %s: error %d "%s" from "%s"',
00227 wfHostname(), $retval, trim($err), $cmd ) );
00228 return new MediaTransformError( 'thumbnail_error', $clientWidth, $clientHeight, $err );
00229 } else {
00230 return new ThumbnailImage( $image, $dstUrl, $clientWidth, $clientHeight, $dstPath );
00231 }
00232 }
00233
00234 static function imageJpegWrapper( $dst_image, $thumbPath ) {
00235 imageinterlace( $dst_image );
00236 imagejpeg( $dst_image, $thumbPath, 95 );
00237 }
00238
00239
00240 function getMetadata( $image, $filename ) {
00241 global $wgShowEXIF;
00242 if( $wgShowEXIF && file_exists( $filename ) ) {
00243 $exif = new Exif( $filename );
00244 $data = $exif->getFilteredData();
00245 if ( $data ) {
00246 $data['MEDIAWIKI_EXIF_VERSION'] = Exif::version();
00247 return serialize( $data );
00248 } else {
00249 return '0';
00250 }
00251 } else {
00252 return '';
00253 }
00254 }
00255
00256 function getMetadataType( $image ) {
00257 return 'exif';
00258 }
00259
00260 function isMetadataValid( $image, $metadata ) {
00261 global $wgShowEXIF;
00262 if ( !$wgShowEXIF ) {
00263 # Metadata disabled and so an empty field is expected
00264 return true;
00265 }
00266 if ( $metadata === '0' ) {
00267 # Special value indicating that there is no EXIF data in the file
00268 return true;
00269 }
00270 $exif = @unserialize( $metadata );
00271 if ( !isset( $exif['MEDIAWIKI_EXIF_VERSION'] ) ||
00272 $exif['MEDIAWIKI_EXIF_VERSION'] != Exif::version() )
00273 {
00274 # Wrong version
00275 wfDebug( __METHOD__.": wrong version\n" );
00276 return false;
00277 }
00278 return true;
00279 }
00280
00288 function visibleMetadataFields() {
00289 $fields = array();
00290 $lines = explode( "\n", wfMsgForContent( 'metadata-fields' ) );
00291 foreach( $lines as $line ) {
00292 $matches = array();
00293 if( preg_match( '/^\\*\s*(.*?)\s*$/', $line, $matches ) ) {
00294 $fields[] = $matches[1];
00295 }
00296 }
00297 $fields = array_map( 'strtolower', $fields );
00298 return $fields;
00299 }
00300
00301 function formatMetadata( $image ) {
00302 $result = array(
00303 'visible' => array(),
00304 'collapsed' => array()
00305 );
00306 $metadata = $image->getMetadata();
00307 if ( !$metadata ) {
00308 return false;
00309 }
00310 $exif = unserialize( $metadata );
00311 if ( !$exif ) {
00312 return false;
00313 }
00314 unset( $exif['MEDIAWIKI_EXIF_VERSION'] );
00315 $format = new FormatExif( $exif );
00316
00317 $formatted = $format->getFormattedData();
00318
00319 $visibleFields = $this->visibleMetadataFields();
00320 foreach ( $formatted as $name => $value ) {
00321 $tag = strtolower( $name );
00322 self::addMeta( $result,
00323 in_array( $tag, $visibleFields ) ? 'visible' : 'collapsed',
00324 'exif',
00325 $tag,
00326 $value
00327 );
00328 }
00329 return $result;
00330 }
00331 }