123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332 |
- <?php
- /**
- * @file
- * @ingroup Media
- */
- /**
- * @ingroup Media
- */
- class BitmapHandler extends ImageHandler {
- function normaliseParams( $image, &$params ) {
- global $wgMaxImageArea;
- if ( !parent::normaliseParams( $image, $params ) ) {
- return false;
- }
- $mimeType = $image->getMimeType();
- $srcWidth = $image->getWidth( $params['page'] );
- $srcHeight = $image->getHeight( $params['page'] );
- # Don't thumbnail an image so big that it will fill hard drives and send servers into swap
- # JPEG has the handy property of allowing thumbnailing without full decompression, so we make
- # an exception for it.
- if ( $mimeType !== 'image/jpeg' &&
- $srcWidth * $srcHeight > $wgMaxImageArea )
- {
- return false;
- }
- # Don't make an image bigger than the source
- $params['physicalWidth'] = $params['width'];
- $params['physicalHeight'] = $params['height'];
- if ( $params['physicalWidth'] >= $srcWidth ) {
- $params['physicalWidth'] = $srcWidth;
- $params['physicalHeight'] = $srcHeight;
- return true;
- }
- return true;
- }
- function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 ) {
- global $wgUseImageMagick, $wgImageMagickConvertCommand, $wgImageMagickTempDir;
- global $wgCustomConvertCommand, $wgUseImageResize;
- global $wgSharpenParameter, $wgSharpenReductionThreshold;
- global $wgMaxAnimatedGifArea;
- if ( !$this->normaliseParams( $image, $params ) ) {
- return new TransformParameterError( $params );
- }
- $physicalWidth = $params['physicalWidth'];
- $physicalHeight = $params['physicalHeight'];
- $clientWidth = $params['width'];
- $clientHeight = $params['height'];
- $srcWidth = $image->getWidth();
- $srcHeight = $image->getHeight();
- $mimeType = $image->getMimeType();
- $srcPath = $image->getPath();
- $retval = 0;
- wfDebug( __METHOD__.": creating {$physicalWidth}x{$physicalHeight} thumbnail at $dstPath\n" );
- if ( !$image->mustRender() && $physicalWidth == $srcWidth && $physicalHeight == $srcHeight ) {
- # normaliseParams (or the user) wants us to return the unscaled image
- wfDebug( __METHOD__.": returning unscaled image\n" );
- return new ThumbnailImage( $image, $image->getURL(), $clientWidth, $clientHeight, $srcPath );
- }
- if ( !$dstPath ) {
- // No output path available, client side scaling only
- $scaler = 'client';
- } elseif( !$wgUseImageResize ) {
- $scaler = 'client';
- } elseif ( $wgUseImageMagick ) {
- $scaler = 'im';
- } elseif ( $wgCustomConvertCommand ) {
- $scaler = 'custom';
- } elseif ( function_exists( 'imagecreatetruecolor' ) ) {
- $scaler = 'gd';
- } else {
- $scaler = 'client';
- }
- wfDebug( __METHOD__.": scaler $scaler\n" );
- if ( $scaler == 'client' ) {
- # Client-side image scaling, use the source URL
- # Using the destination URL in a TRANSFORM_LATER request would be incorrect
- return new ThumbnailImage( $image, $image->getURL(), $clientWidth, $clientHeight, $srcPath );
- }
- if ( $flags & self::TRANSFORM_LATER ) {
- wfDebug( __METHOD__.": Transforming later per flags.\n" );
- return new ThumbnailImage( $image, $dstUrl, $clientWidth, $clientHeight, $dstPath );
- }
- if ( !wfMkdirParents( dirname( $dstPath ) ) ) {
- wfDebug( __METHOD__.": Unable to create thumbnail destination directory, falling back to client scaling\n" );
- return new ThumbnailImage( $image, $image->getURL(), $clientWidth, $clientHeight, $srcPath );
- }
- if ( $scaler == 'im' ) {
- # use ImageMagick
- $quality = '';
- $sharpen = '';
- $frame = '';
- $animation = '';
- if ( $mimeType == 'image/jpeg' ) {
- $quality = "-quality 80"; // 80%
- # Sharpening, see bug 6193
- if ( ( $physicalWidth + $physicalHeight ) / ( $srcWidth + $srcHeight ) < $wgSharpenReductionThreshold ) {
- $sharpen = "-sharpen " . wfEscapeShellArg( $wgSharpenParameter );
- }
- } elseif ( $mimeType == 'image/png' ) {
- $quality = "-quality 95"; // zlib 9, adaptive filtering
- } elseif( $mimeType == 'image/gif' ) {
- if( $srcWidth * $srcHeight > $wgMaxAnimatedGifArea ) {
- // Extract initial frame only; we're so big it'll
- // be a total drag. :P
- $frame = '[0]';
- } else {
- // Coalesce is needed to scale animated GIFs properly (bug 1017).
- $animation = ' -coalesce ';
- }
- }
- if ( strval( $wgImageMagickTempDir ) !== '' ) {
- $tempEnv = 'MAGICK_TMPDIR=' . wfEscapeShellArg( $wgImageMagickTempDir ) . ' ';
- } else {
- $tempEnv = '';
- }
- # Specify white background color, will be used for transparent images
- # in Internet Explorer/Windows instead of default black.
- # Note, we specify "-size {$physicalWidth}" and NOT "-size {$physicalWidth}x{$physicalHeight}".
- # It seems that ImageMagick has a bug wherein it produces thumbnails of
- # the wrong size in the second case.
- $cmd =
- $tempEnv .
- wfEscapeShellArg($wgImageMagickConvertCommand) .
- " {$quality} -background white -size {$physicalWidth} ".
- wfEscapeShellArg($srcPath . $frame) .
- $animation .
- // For the -resize option a "!" is needed to force exact size,
- // or ImageMagick may decide your ratio is wrong and slice off
- // a pixel.
- " -thumbnail " . wfEscapeShellArg( "{$physicalWidth}x{$physicalHeight}!" ) .
- " -depth 8 $sharpen " .
- wfEscapeShellArg($dstPath) . " 2>&1";
- wfDebug( __METHOD__.": running ImageMagick: $cmd\n");
- wfProfileIn( 'convert' );
- $err = wfShellExec( $cmd, $retval );
- wfProfileOut( 'convert' );
- } elseif( $scaler == 'custom' ) {
- # Use a custom convert command
- # Variables: %s %d %w %h
- $src = wfEscapeShellArg( $srcPath );
- $dst = wfEscapeShellArg( $dstPath );
- $cmd = $wgCustomConvertCommand;
- $cmd = str_replace( '%s', $src, str_replace( '%d', $dst, $cmd ) ); # Filenames
- $cmd = str_replace( '%h', $physicalHeight, str_replace( '%w', $physicalWidth, $cmd ) ); # Size
- wfDebug( __METHOD__.": Running custom convert command $cmd\n" );
- wfProfileIn( 'convert' );
- $err = wfShellExec( $cmd, $retval );
- wfProfileOut( 'convert' );
- } else /* $scaler == 'gd' */ {
- # Use PHP's builtin GD library functions.
- #
- # First find out what kind of file this is, and select the correct
- # input routine for this.
- $typemap = array(
- 'image/gif' => array( 'imagecreatefromgif', 'palette', 'imagegif' ),
- 'image/jpeg' => array( 'imagecreatefromjpeg', 'truecolor', array( __CLASS__, 'imageJpegWrapper' ) ),
- 'image/png' => array( 'imagecreatefrompng', 'bits', 'imagepng' ),
- 'image/vnd.wap.wbmp' => array( 'imagecreatefromwbmp', 'palette', 'imagewbmp' ),
- 'image/xbm' => array( 'imagecreatefromxbm', 'palette', 'imagexbm' ),
- );
- if( !isset( $typemap[$mimeType] ) ) {
- $err = 'Image type not supported';
- wfDebug( "$err\n" );
- return new MediaTransformError( 'thumbnail_error', $clientWidth, $clientHeight, $err );
- }
- list( $loader, $colorStyle, $saveType ) = $typemap[$mimeType];
- if( !function_exists( $loader ) ) {
- $err = "Incomplete GD library configuration: missing function $loader";
- wfDebug( "$err\n" );
- return new MediaTransformError( 'thumbnail_error', $clientWidth, $clientHeight, $err );
- }
- $src_image = call_user_func( $loader, $srcPath );
- $dst_image = imagecreatetruecolor( $physicalWidth, $physicalHeight );
- // Initialise the destination image to transparent instead of
- // the default solid black, to support PNG and GIF transparency nicely
- $background = imagecolorallocate( $dst_image, 0, 0, 0 );
- imagecolortransparent( $dst_image, $background );
- imagealphablending( $dst_image, false );
- if( $colorStyle == 'palette' ) {
- // Don't resample for paletted GIF images.
- // It may just uglify them, and completely breaks transparency.
- imagecopyresized( $dst_image, $src_image,
- 0,0,0,0,
- $physicalWidth, $physicalHeight, imagesx( $src_image ), imagesy( $src_image ) );
- } else {
- imagecopyresampled( $dst_image, $src_image,
- 0,0,0,0,
- $physicalWidth, $physicalHeight, imagesx( $src_image ), imagesy( $src_image ) );
- }
- imagesavealpha( $dst_image, true );
- call_user_func( $saveType, $dst_image, $dstPath );
- imagedestroy( $dst_image );
- imagedestroy( $src_image );
- $retval = 0;
- }
- $removed = $this->removeBadFile( $dstPath, $retval );
- if ( $retval != 0 || $removed ) {
- wfDebugLog( 'thumbnail',
- sprintf( 'thumbnail failed on %s: error %d "%s" from "%s"',
- wfHostname(), $retval, trim($err), $cmd ) );
- return new MediaTransformError( 'thumbnail_error', $clientWidth, $clientHeight, $err );
- } else {
- return new ThumbnailImage( $image, $dstUrl, $clientWidth, $clientHeight, $dstPath );
- }
- }
- static function imageJpegWrapper( $dst_image, $thumbPath ) {
- imageinterlace( $dst_image );
- imagejpeg( $dst_image, $thumbPath, 95 );
- }
- function getMetadata( $image, $filename ) {
- global $wgShowEXIF;
- if( $wgShowEXIF && file_exists( $filename ) ) {
- $exif = new Exif( $filename );
- $data = $exif->getFilteredData();
- if ( $data ) {
- $data['MEDIAWIKI_EXIF_VERSION'] = Exif::version();
- return serialize( $data );
- } else {
- return '0';
- }
- } else {
- return '';
- }
- }
- function getMetadataType( $image ) {
- return 'exif';
- }
- function isMetadataValid( $image, $metadata ) {
- global $wgShowEXIF;
- if ( !$wgShowEXIF ) {
- # Metadata disabled and so an empty field is expected
- return true;
- }
- if ( $metadata === '0' ) {
- # Special value indicating that there is no EXIF data in the file
- return true;
- }
- $exif = @unserialize( $metadata );
- if ( !isset( $exif['MEDIAWIKI_EXIF_VERSION'] ) ||
- $exif['MEDIAWIKI_EXIF_VERSION'] != Exif::version() )
- {
- # Wrong version
- wfDebug( __METHOD__.": wrong version\n" );
- return false;
- }
- return true;
- }
- /**
- * Get a list of EXIF metadata items which should be displayed when
- * the metadata table is collapsed.
- *
- * @return array of strings
- * @access private
- */
- function visibleMetadataFields() {
- $fields = array();
- $lines = explode( "\n", wfMsgForContent( 'metadata-fields' ) );
- foreach( $lines as $line ) {
- $matches = array();
- if( preg_match( '/^\\*\s*(.*?)\s*$/', $line, $matches ) ) {
- $fields[] = $matches[1];
- }
- }
- $fields = array_map( 'strtolower', $fields );
- return $fields;
- }
- function formatMetadata( $image ) {
- $result = array(
- 'visible' => array(),
- 'collapsed' => array()
- );
- $metadata = $image->getMetadata();
- if ( !$metadata ) {
- return false;
- }
- $exif = unserialize( $metadata );
- if ( !$exif ) {
- return false;
- }
- unset( $exif['MEDIAWIKI_EXIF_VERSION'] );
- $format = new FormatExif( $exif );
- $formatted = $format->getFormattedData();
- // Sort fields into visible and collapsed
- $visibleFields = $this->visibleMetadataFields();
- foreach ( $formatted as $name => $value ) {
- $tag = strtolower( $name );
- self::addMeta( $result,
- in_array( $tag, $visibleFields ) ? 'visible' : 'collapsed',
- 'exif',
- $tag,
- $value
- );
- }
- return $result;
- }
- }
|