46 $mainConfig = MediaWikiServices::getInstance()->getMainConfig();
47 $useImageResize = $mainConfig->get( MainConfigNames::UseImageResize );
48 $useImageMagick = $mainConfig->get( MainConfigNames::UseImageMagick );
49 $customConvertCommand = $mainConfig->get( MainConfigNames::CustomConvertCommand );
50 if ( !$dstPath && $checkDstPath ) {
51 # No output path available, client side scaling only
53 } elseif ( !$useImageResize ) {
55 } elseif ( $useImageMagick ) {
57 } elseif ( $customConvertCommand ) {
59 } elseif ( function_exists(
'imagecreatetruecolor' ) ) {
61 } elseif ( class_exists(
'Imagick' ) ) {
75 $res = parent::makeParamString( $params );
76 if ( isset( $params[
'interlace'] ) && $params[
'interlace'] ) {
77 return "interlaced-{$res}";
88 $remainder = preg_replace(
'/^interlaced-/',
'', $str );
89 $params = parent::parseParamString( $remainder );
90 if ( $params ===
false ) {
93 $params[
'interlace'] = $str !== $remainder;
102 if ( $name ===
'interlace' ) {
103 return $value ===
false || $value ===
true;
105 return parent::validateParam( $name, $value );
116 $maxInterlacingAreas = MediaWikiServices::getInstance()->getMainConfig()
117 ->get( MainConfigNames::MaxInterlacingAreas );
118 if ( !parent::normaliseParams( $image, $params ) ) {
121 $mimeType = $image->getMimeType();
122 $interlace = isset( $params[
'interlace'] ) && $params[
'interlace']
123 && isset( $maxInterlacingAreas[$mimeType] )
124 && $this->
getImageArea( $image ) <= $maxInterlacingAreas[$mimeType];
125 $params[
'interlace'] = $interlace;
136 switch ( $pixelFormat ) {
138 return [
'1x1',
'1x1',
'1x1' ];
140 return [
'2x1',
'1x1',
'1x1' ];
142 return [
'2x2',
'1x1',
'1x1' ];
144 throw new MWException(
'Invalid pixel format for JPEG output' );
159 $mainConfig = MediaWikiServices::getInstance()->getMainConfig();
160 $sharpenReductionThreshold = $mainConfig->get( MainConfigNames::SharpenReductionThreshold );
161 $sharpenParameter = $mainConfig->get( MainConfigNames::SharpenParameter );
162 $maxAnimatedGifArea = $mainConfig->get( MainConfigNames::MaxAnimatedGifArea );
163 $imageMagickTempDir = $mainConfig->get( MainConfigNames::ImageMagickTempDir );
164 $imageMagickConvertCommand = $mainConfig->get( MainConfigNames::ImageMagickConvertCommand );
165 $jpegPixelFormat = $mainConfig->get( MainConfigNames::JpegPixelFormat );
166 $jpegQuality = $mainConfig->get( MainConfigNames::JpegQuality );
171 $animation_post = [];
175 if ( $params[
'mimeType'] ==
'image/jpeg' ) {
176 $qualityVal = isset( $params[
'quality'] ) ? (string)$params[
'quality'] :
null;
177 $quality = [
'-quality', $qualityVal ?: (string)$jpegQuality ];
178 if ( $params[
'interlace'] ) {
179 $animation_post = [
'-interlace',
'JPEG' ];
181 # Sharpening, see T8193
182 if ( ( $params[
'physicalWidth'] + $params[
'physicalHeight'] )
183 / ( $params[
'srcWidth'] + $params[
'srcHeight'] )
184 < $sharpenReductionThreshold
186 $sharpen = [
'-sharpen', $sharpenParameter ];
190 $decoderHint = [
'-define',
"jpeg:size={$params['physicalDimensions']}" ];
192 if ( $jpegPixelFormat ) {
194 $subsampling = [
'-sampling-factor', implode(
',', $factors ) ];
196 } elseif ( $params[
'mimeType'] ==
'image/png' ) {
197 $quality = [
'-quality',
'95' ];
198 if ( $params[
'interlace'] ) {
199 $animation_post = [
'-interlace',
'PNG' ];
201 } elseif ( $params[
'mimeType'] ==
'image/webp' ) {
202 $quality = [
'-quality',
'95' ];
203 } elseif ( $params[
'mimeType'] ==
'image/gif' ) {
204 if ( $this->
getImageArea( $image ) > $maxAnimatedGifArea ) {
210 $animation_pre = [
'-coalesce' ];
214 $animation_post = [
'-fuzz',
'5%',
'-layers',
'optimizeTransparency' ];
219 $animation_post[] =
'-interlace';
220 $animation_post[] =
'GIF';
222 } elseif ( $params[
'mimeType'] ==
'image/x-xcf' ) {
230 '-background',
'transparent',
232 '-background',
'white',
237 $env = [
'OMP_NUM_THREADS' => 1 ];
238 if ( strval( $imageMagickTempDir ) !==
'' ) {
239 $env[
'MAGICK_TMPDIR'] = $imageMagickTempDir;
242 $rotation = isset( $params[
'disableRotation'] ) ? 0 : $this->
getRotation( $image );
245 $cmd = Shell::escape( ...array_merge(
246 [ $imageMagickConvertCommand ],
250 [
'-background',
'white' ],
257 [
'-thumbnail',
"{$width}x{$height}!" ],
259 ( $params[
'comment'] !==
''
263 [
'+set',
'Thumb::URI' ],
266 [
'-rotate',
"-$rotation" ],
271 wfDebug( __METHOD__ .
": running ImageMagick: $cmd" );
275 if ( $retval !== 0 ) {
281 return false; # No error
293 $mainConfig = MediaWikiServices::getInstance()->getMainConfig();
294 $sharpenReductionThreshold = $mainConfig->get( MainConfigNames::SharpenReductionThreshold );
295 $sharpenParameter = $mainConfig->get( MainConfigNames::SharpenParameter );
296 $maxAnimatedGifArea = $mainConfig->get( MainConfigNames::MaxAnimatedGifArea );
297 $jpegPixelFormat = $mainConfig->get( MainConfigNames::JpegPixelFormat );
298 $jpegQuality = $mainConfig->get( MainConfigNames::JpegQuality );
301 $im->readImage( $params[
'srcPath'] );
303 if ( $params[
'mimeType'] ==
'image/jpeg' ) {
305 if ( ( $params[
'physicalWidth'] + $params[
'physicalHeight'] )
306 / ( $params[
'srcWidth'] + $params[
'srcHeight'] )
307 < $sharpenReductionThreshold
310 list( $radius, $sigma ) = explode(
'x', $sharpenParameter, 2 );
311 $im->sharpenImage( (
float)$radius, (
float)$sigma );
313 $qualityVal = isset( $params[
'quality'] ) ? (string)$params[
'quality'] :
null;
314 $im->setCompressionQuality( $qualityVal ?: $jpegQuality );
315 if ( $params[
'interlace'] ) {
316 $im->setInterlaceScheme( Imagick::INTERLACE_JPEG );
318 if ( $jpegPixelFormat ) {
320 $im->setSamplingFactors( $factors );
322 } elseif ( $params[
'mimeType'] ==
'image/png' ) {
323 $im->setCompressionQuality( 95 );
324 if ( $params[
'interlace'] ) {
325 $im->setInterlaceScheme( Imagick::INTERLACE_PNG );
327 } elseif ( $params[
'mimeType'] ==
'image/gif' ) {
328 if ( $this->
getImageArea( $image ) > $maxAnimatedGifArea ) {
331 $im->setImageScene( 0 );
334 $im = $im->coalesceImages();
337 if ( $params[
'interlace'] ) {
338 $im->setInterlaceScheme( Imagick::INTERLACE_GIF );
342 $rotation = isset( $params[
'disableRotation'] ) ? 0 : $this->
getRotation( $image );
345 $im->setImageBackgroundColor(
new ImagickPixel(
'white' ) );
348 foreach ( $im as $i => $frame ) {
349 if ( !$frame->thumbnailImage( $width, $height,
false ) ) {
353 $im->setImageDepth( 8 );
355 if ( $rotation && !$im->rotateImage(
new ImagickPixel(
'white' ), 360 - $rotation ) ) {
360 wfDebug( __METHOD__ .
": Writing animated thumbnail" );
362 $result = $im->writeImages( $params[
'dstPath'],
true );
364 $result = $im->writeImage( $params[
'dstPath'] );
368 "Unable to write thumbnail to {$params['dstPath']}" );
370 }
catch ( ImagickException $e ) {
387 $customConvertCommand = MediaWikiServices::getInstance()->getMainConfig()
388 ->get( MainConfigNames::CustomConvertCommand );
391 $matchLookupTable = [
392 '%d' => Shell::escape( $params[
'dstPath'] ),
393 '%s' => Shell::escape( $params[
'srcPath'] ),
394 '%w' => Shell::escape( $params[
'physicalWidth'] ),
395 '%h' => Shell::escape( $params[
'physicalHeight'] ),
399 $cmd = preg_replace_callback(
'/%[dswh]/',
400 static function ( $m ) use ( &$matchLookupTable ) {
401 if ( !isset( $matchLookupTable[$m[0]] ) ) {
405 $replacement = $matchLookupTable[$m[0]];
406 unset( $matchLookupTable[$m[0]] );
409 $customConvertCommand
411 wfDebug( __METHOD__ .
": Running custom convert command $cmd" );
415 if ( $retval !== 0 ) {
421 return false; # No error
433 # Use PHP's builtin GD library functions.
434 # First find out what kind of file this is, and select the correct
435 # input routine for this.
438 'image/gif' => [
'imagecreatefromgif',
'palette',
false,
'imagegif' ],
439 'image/jpeg' => [
'imagecreatefromjpeg',
'truecolor',
true,
440 [ __CLASS__,
'imageJpegWrapper' ] ],
441 'image/png' => [
'imagecreatefrompng',
'bits',
false,
'imagepng' ],
442 'image/vnd.wap.wbmp' => [
'imagecreatefromwbmp',
'palette',
false,
'imagewbmp' ],
443 'image/xbm' => [
'imagecreatefromxbm',
'palette',
false,
'imagexbm' ],
446 if ( !isset( $typemap[$params[
'mimeType']] ) ) {
447 $err =
'Image type not supported';
449 $errMsg =
wfMessage(
'thumbnail_image-type' )->text();
453 list( $loader, $colorStyle, $useQuality, $saveType ) = $typemap[$params[
'mimeType']];
455 if ( !function_exists( $loader ) ) {
456 $err =
"Incomplete GD library configuration: missing function $loader";
458 $errMsg =
wfMessage(
'thumbnail_gd-library', $loader )->text();
463 if ( !file_exists( $params[
'srcPath'] ) ) {
464 $err =
"File seems to be missing: {$params['srcPath']}";
466 $errMsg =
wfMessage(
'thumbnail_image-missing', $params[
'srcPath'] )->text();
471 if ( filesize( $params[
'srcPath'] ) === 0 ) {
472 $err =
"Image file size seems to be zero.";
474 $errMsg =
wfMessage(
'thumbnail_image-size-zero', $params[
'srcPath'] )->text();
479 $src_image = $loader( $params[
'srcPath'] );
481 $rotation = function_exists(
'imagerotate' ) && !isset( $params[
'disableRotation'] ) ?
485 $dst_image = imagecreatetruecolor( $width, $height );
489 $background = imagecolorallocate( $dst_image, 0, 0, 0 );
490 imagecolortransparent( $dst_image, $background );
491 imagealphablending( $dst_image,
false );
493 if ( $colorStyle ==
'palette' ) {
496 imagecopyresized( $dst_image, $src_image,
499 imagesx( $src_image ), imagesy( $src_image ) );
501 imagecopyresampled( $dst_image, $src_image,
504 imagesx( $src_image ), imagesy( $src_image ) );
507 if ( $rotation % 360 != 0 && $rotation % 90 == 0 ) {
508 $rot_image = imagerotate( $dst_image, $rotation, 0 );
509 imagedestroy( $dst_image );
510 $dst_image = $rot_image;
513 imagesavealpha( $dst_image,
true );
515 $funcParams = [ $dst_image, $params[
'dstPath'] ];
516 if ( $useQuality && isset( $params[
'quality'] ) ) {
517 $funcParams[] = $params[
'quality'];
520 $saveType( ...$funcParams );
522 imagedestroy( $dst_image );
523 imagedestroy( $src_image );
525 return false; # No error
538 $jpegQuality = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::JpegQuality );
540 if ( $quality ===
null ) {
541 $quality = $jpegQuality;
544 imageinterlace( $dst_image );
545 imagejpeg( $dst_image, $thumbPath, $quality );
558 # ImageMagick supports autorotation
561 # Imagick::rotateImage
564 # GD's imagerotate function is used to rotate images, but not
565 # all precompiled PHP versions have that function
566 return function_exists(
'imagerotate' );
568 # Other scalers don't support rotation
579 $enableAutoRotation = MediaWikiServices::getInstance()->getMainConfig()
580 ->get( MainConfigNames::EnableAutoRotation );
582 if ( $enableAutoRotation ===
null ) {
587 return $enableAutoRotation;
599 $imageMagickConvertCommand = MediaWikiServices::getInstance()
600 ->getMainConfig()->get( MainConfigNames::ImageMagickConvertCommand );
608 $cmd = Shell::escape( $imageMagickConvertCommand ) .
" " .
610 " -rotate " . Shell::escape(
"-$rotation" ) .
" " .
612 wfDebug( __METHOD__ .
": running ImageMagick: $cmd" );
615 if ( $retval !== 0 ) {
624 $im->readImage( $params[
'srcPath'] );
625 if ( !$im->rotateImage(
new ImagickPixel(
'white' ), 360 - $rotation ) ) {
627 "Error rotating $rotation degrees" );
629 $result = $im->writeImage( $params[
'dstPath'] );
632 "Unable to write image to {$params['dstPath']}" );
638 "$scaler rotation not implemented" );
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfShellExecWithStderr( $cmd, &$retval=null, $environ=[], $limits=[])
Execute a shell command, returning both stdout and stderr.
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
Generic handler for bitmap images.
canRotate()
Returns whether the current scaler supports rotation (im and gd do)
static imageJpegWrapper( $dst_image, $thumbPath, $quality=null)
Callback for transformGd when transforming jpeg images.
transformImageMagick( $image, $params)
Transform an image using ImageMagick.
getScalerType( $dstPath, $checkDstPath=true)
Returns which scaler type should be used.
imageMagickSubsampling( $pixelFormat)
Get ImageMagick subsampling factors for the target JPEG pixel format.
transformCustom( $image, $params)
Transform an image using a custom command.
makeParamString( $params)
Merge a parameter array into a string appropriate for inclusion in filenames.stringto override
transformGd( $image, $params)
Transform an image using the built in GD library.
parseParamString( $str)
Parse a param string made with makeParamString back into an array.array|false Array of parameters or ...
normaliseParams( $image, &$params)
transformImageMagickExt( $image, $params)
Transform an image using the Imagick PHP extension.
validateParam( $name, $value)
Validate a thumbnail parameter at parse time.Return true to accept the parameter, and false to reject...
getImageArea( $image)
Function that returns the number of pixels to be thumbnailed.
A class containing constants representing the names of configuration variables.
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.