2007-04-20 12:31:36 +00:00
|
|
|
<?php
|
WARNING: HUGE COMMIT
Doxygen documentation update:
* Changed alls @addtogroup to @ingroup. @addtogroup adds the comment to the group description, but doesn't add the file, class, function, ... to the group like @ingroup does. See for example http://svn.wikimedia.org/doc/group__SpecialPage.html where it's impossible to see related files, classes, ... that should belong to that group.
* Added @file to file description, it seems that it should be explicitely decalred for file descriptions, otherwise doxygen will think that the comment document the first class, variabled, function, ... that is in that file.
* Removed some empty comments
* Removed some ?>
Added following groups:
* ExternalStorage
* JobQueue
* MaintenanceLanguage
One more thing: there are still a lot of warnings when generating the doc.
2008-05-20 17:13:28 +00:00
|
|
|
/**
|
|
|
|
|
* @file
|
|
|
|
|
* @ingroup Media
|
|
|
|
|
*/
|
2007-04-20 12:31:36 +00:00
|
|
|
|
2007-04-24 06:53:31 +00:00
|
|
|
/**
|
WARNING: HUGE COMMIT
Doxygen documentation update:
* Changed alls @addtogroup to @ingroup. @addtogroup adds the comment to the group description, but doesn't add the file, class, function, ... to the group like @ingroup does. See for example http://svn.wikimedia.org/doc/group__SpecialPage.html where it's impossible to see related files, classes, ... that should belong to that group.
* Added @file to file description, it seems that it should be explicitely decalred for file descriptions, otherwise doxygen will think that the comment document the first class, variabled, function, ... that is in that file.
* Removed some empty comments
* Removed some ?>
Added following groups:
* ExternalStorage
* JobQueue
* MaintenanceLanguage
One more thing: there are still a lot of warnings when generating the doc.
2008-05-20 17:13:28 +00:00
|
|
|
* @ingroup Media
|
2007-04-24 06:53:31 +00:00
|
|
|
*/
|
2007-04-20 12:31:36 +00:00
|
|
|
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'];
|
2008-04-14 07:45:50 +00:00
|
|
|
|
2007-04-20 12:31:36 +00:00
|
|
|
if ( $params['physicalWidth'] >= $srcWidth ) {
|
|
|
|
|
$params['physicalWidth'] = $srcWidth;
|
|
|
|
|
$params['physicalHeight'] = $srcHeight;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2008-04-14 07:45:50 +00:00
|
|
|
|
2007-04-20 12:31:36 +00:00
|
|
|
function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 ) {
|
|
|
|
|
global $wgUseImageMagick, $wgImageMagickConvertCommand;
|
|
|
|
|
global $wgCustomConvertCommand;
|
|
|
|
|
global $wgSharpenParameter, $wgSharpenReductionThreshold;
|
|
|
|
|
|
|
|
|
|
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();
|
2007-05-30 21:02:32 +00:00
|
|
|
$srcPath = $image->getPath();
|
2007-04-20 12:31:36 +00:00
|
|
|
$retval = 0;
|
|
|
|
|
wfDebug( __METHOD__.": creating {$physicalWidth}x{$physicalHeight} thumbnail at $dstPath\n" );
|
|
|
|
|
|
2008-08-27 18:25:24 +00:00
|
|
|
if ( !$image->mustRender() && $physicalWidth == $srcWidth && $physicalHeight == $srcHeight ) {
|
2007-04-20 12:31:36 +00:00
|
|
|
# normaliseParams (or the user) wants us to return the unscaled image
|
|
|
|
|
wfDebug( __METHOD__.": returning unscaled image\n" );
|
2007-08-31 02:51:23 +00:00
|
|
|
return new ThumbnailImage( $image, $image->getURL(), $clientWidth, $clientHeight, $srcPath );
|
2007-04-20 12:31:36 +00:00
|
|
|
}
|
|
|
|
|
|
2007-05-30 21:02:32 +00:00
|
|
|
if ( !$dstPath ) {
|
|
|
|
|
// No output path available, client side scaling only
|
|
|
|
|
$scaler = 'client';
|
|
|
|
|
} elseif ( $wgUseImageMagick ) {
|
2007-04-20 12:31:36 +00:00
|
|
|
$scaler = 'im';
|
|
|
|
|
} elseif ( $wgCustomConvertCommand ) {
|
|
|
|
|
$scaler = 'custom';
|
|
|
|
|
} elseif ( function_exists( 'imagecreatetruecolor' ) ) {
|
|
|
|
|
$scaler = 'gd';
|
|
|
|
|
} else {
|
|
|
|
|
$scaler = 'client';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( $scaler == 'client' ) {
|
|
|
|
|
# Client-side image scaling, use the source URL
|
|
|
|
|
# Using the destination URL in a TRANSFORM_LATER request would be incorrect
|
2007-08-31 02:51:23 +00:00
|
|
|
return new ThumbnailImage( $image, $image->getURL(), $clientWidth, $clientHeight, $srcPath );
|
2007-04-20 12:31:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( $flags & self::TRANSFORM_LATER ) {
|
2007-08-31 02:51:23 +00:00
|
|
|
return new ThumbnailImage( $image, $dstUrl, $clientWidth, $clientHeight, $dstPath );
|
2007-04-20 12:31:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( !wfMkdirParents( dirname( $dstPath ) ) ) {
|
2008-02-27 07:06:32 +00:00
|
|
|
wfDebug( "Unable to create thumbnail destination directory, falling back to client scaling\n" );
|
|
|
|
|
return new ThumbnailImage( $image, $image->getURL(), $clientWidth, $clientHeight, $srcPath );
|
2007-04-20 12:31:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( $scaler == 'im' ) {
|
|
|
|
|
# use ImageMagick
|
|
|
|
|
|
|
|
|
|
$sharpen = '';
|
|
|
|
|
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
|
|
|
|
|
} else {
|
|
|
|
|
$quality = ''; // default
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# 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 = wfEscapeShellArg($wgImageMagickConvertCommand) .
|
|
|
|
|
" {$quality} -background white -size {$physicalWidth} ".
|
|
|
|
|
wfEscapeShellArg($srcPath) .
|
|
|
|
|
// Coalesce is needed to scale animated GIFs properly (bug 1017).
|
|
|
|
|
' -coalesce ' .
|
|
|
|
|
// 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 );
|
|
|
|
|
}
|
2007-08-23 18:28:21 +00:00
|
|
|
list( $loader, $colorStyle, $saveType ) = $typemap[$mimeType];
|
2007-04-20 12:31:36 +00:00
|
|
|
|
|
|
|
|
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 );
|
2008-04-14 07:45:50 +00:00
|
|
|
|
2007-08-23 18:28:21 +00:00
|
|
|
// 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 );
|
2008-04-14 07:45:50 +00:00
|
|
|
imagealphablending( $dst_image, false );
|
2007-08-23 18:28:21 +00:00
|
|
|
|
|
|
|
|
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 ) );
|
|
|
|
|
}
|
2007-08-22 00:30:16 +00:00
|
|
|
|
2007-08-23 18:28:21 +00:00
|
|
|
imagesavealpha( $dst_image, true );
|
2008-04-14 07:45:50 +00:00
|
|
|
|
2007-04-20 12:31:36 +00:00
|
|
|
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 {
|
2007-08-31 02:51:23 +00:00
|
|
|
return new ThumbnailImage( $image, $dstUrl, $clientWidth, $clientHeight, $dstPath );
|
2007-04-20 12:31:36 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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 );
|
2007-04-20 21:35:27 +00:00
|
|
|
$data = $exif->getFilteredData();
|
|
|
|
|
if ( $data ) {
|
|
|
|
|
$data['MEDIAWIKI_EXIF_VERSION'] = Exif::version();
|
|
|
|
|
return serialize( $data );
|
|
|
|
|
} else {
|
|
|
|
|
return '0';
|
|
|
|
|
}
|
2007-04-20 12:31:36 +00:00
|
|
|
} 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
|
2007-04-27 09:43:20 +00:00
|
|
|
wfDebug( __METHOD__.": wrong version\n" );
|
2007-04-20 12:31:36 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2007-07-28 01:15:35 +00:00
|
|
|
/**
|
|
|
|
|
* 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 ) {
|
2007-07-29 23:37:14 +00:00
|
|
|
$tag = strtolower( $name );
|
2007-07-28 01:15:35 +00:00
|
|
|
self::addMeta( $result,
|
|
|
|
|
in_array( $tag, $visibleFields ) ? 'visible' : 'collapsed',
|
|
|
|
|
'exif',
|
2007-07-29 23:39:54 +00:00
|
|
|
$tag,
|
2007-07-28 01:15:35 +00:00
|
|
|
$value
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
return $result;
|
|
|
|
|
}
|
2007-04-20 12:31:36 +00:00
|
|
|
}
|