47 $mainConfig = MediaWikiServices::getInstance()->getMainConfig();
48 $useImageResize = $mainConfig->get( MainConfigNames::UseImageResize );
49 $useImageMagick = $mainConfig->get( MainConfigNames::UseImageMagick );
50 $customConvertCommand = $mainConfig->get( MainConfigNames::CustomConvertCommand );
51 if ( !$dstPath && $checkDstPath ) {
52 # No output path available, client side scaling only
54 } elseif ( !$useImageResize ) {
56 } elseif ( $useImageMagick ) {
58 } elseif ( $customConvertCommand ) {
60 } elseif ( $this->
hasGDSupport() && function_exists(
'imagecreatetruecolor' ) ) {
62 } elseif ( class_exists(
'Imagick' ) ) {
86 $res = parent::makeParamString( $params );
87 if ( isset( $params[
'interlace'] ) && $params[
'interlace'] ) {
88 return "interlaced-$res";
98 $remainder = preg_replace(
'/^interlaced-/',
'', $str );
99 $params = parent::parseParamString( $remainder );
100 if ( $params ===
false ) {
103 $params[
'interlace'] = $str !== $remainder;
112 if ( $name ===
'interlace' ) {
113 return $value ===
false || $value ===
true;
115 return parent::validateParam( $name, $value );
125 $maxInterlacingAreas = MediaWikiServices::getInstance()->getMainConfig()
126 ->get( MainConfigNames::MaxInterlacingAreas );
127 if ( !parent::normaliseParams( $image, $params ) ) {
130 $mimeType = $image->getMimeType();
131 $interlace = isset( $params[
'interlace'] ) && $params[
'interlace']
132 && isset( $maxInterlacingAreas[$mimeType] )
133 && $this->
getImageArea( $image ) <= $maxInterlacingAreas[$mimeType];
134 $params[
'interlace'] = $interlace;
145 switch ( $pixelFormat ) {
147 return [
'1x1',
'1x1',
'1x1' ];
149 return [
'2x1',
'1x1',
'1x1' ];
151 return [
'2x2',
'1x1',
'1x1' ];
153 throw new UnexpectedValueException(
'Invalid pixel format for JPEG output' );
168 $mainConfig = MediaWikiServices::getInstance()->getMainConfig();
169 $sharpenReductionThreshold = $mainConfig->get( MainConfigNames::SharpenReductionThreshold );
170 $sharpenParameter = $mainConfig->get( MainConfigNames::SharpenParameter );
171 $maxAnimatedGifArea = $mainConfig->get( MainConfigNames::MaxAnimatedGifArea );
172 $imageMagickTempDir = $mainConfig->get( MainConfigNames::ImageMagickTempDir );
173 $imageMagickConvertCommand = $mainConfig->get( MainConfigNames::ImageMagickConvertCommand );
174 $jpegPixelFormat = $mainConfig->get( MainConfigNames::JpegPixelFormat );
175 $jpegQuality = $mainConfig->get( MainConfigNames::JpegQuality );
180 $animation_post = [];
184 if ( $params[
'mimeType'] ===
'image/jpeg' ) {
185 $qualityVal = isset( $params[
'quality'] ) ? (string)$params[
'quality'] :
null;
186 $quality = [
'-quality', $qualityVal ?: (string)$jpegQuality ];
187 if ( $params[
'interlace'] ) {
188 $animation_post = [
'-interlace',
'JPEG' ];
190 # Sharpening, see T8193
191 if ( ( $params[
'physicalWidth'] + $params[
'physicalHeight'] )
192 / ( $params[
'srcWidth'] + $params[
'srcHeight'] )
193 < $sharpenReductionThreshold
195 $sharpen = [
'-sharpen', $sharpenParameter ];
199 $decoderHint = [
'-define',
"jpeg:size={$params['physicalDimensions']}" ];
201 if ( $jpegPixelFormat ) {
203 $subsampling = [
'-sampling-factor', implode(
',', $factors ) ];
205 } elseif ( $params[
'mimeType'] ===
'image/png' ) {
206 $quality = [
'-quality',
'95' ];
207 if ( $params[
'interlace'] ) {
208 $animation_post = [
'-interlace',
'PNG' ];
210 } elseif ( $params[
'mimeType'] ===
'image/webp' ) {
211 $quality = [
'-quality',
'95' ];
212 } elseif ( $params[
'mimeType'] ===
'image/gif' ) {
213 if ( $this->
getImageArea( $image ) > $maxAnimatedGifArea ) {
219 $animation_pre = [
'-coalesce' ];
223 $animation_post = [
'-fuzz',
'5%',
'-layers',
'optimizeTransparency' ];
228 $animation_post[] =
'-interlace';
229 $animation_post[] =
'GIF';
231 } elseif ( $params[
'mimeType'] ===
'image/x-xcf' ) {
239 '-background',
'transparent',
241 '-background',
'white',
246 $env = [
'OMP_NUM_THREADS' => 1 ];
247 if ( (
string)$imageMagickTempDir !==
'' ) {
248 $env[
'MAGICK_TMPDIR'] = $imageMagickTempDir;
251 $rotation = isset( $params[
'disableRotation'] ) ? 0 : $this->
getRotation( $image );
254 $cmd = Shell::escape( ...array_merge(
255 [ $imageMagickConvertCommand ],
259 [
'-background',
'white' ],
266 [
'-thumbnail',
"{$width}x{$height}!" ],
268 ( $params[
'comment'] !==
''
272 [
'+set',
'Thumb::URI' ],
275 [
'-rotate',
"-$rotation" ],
280 wfDebug( __METHOD__ .
": running ImageMagick: $cmd" );
284 if ( $retval !== 0 ) {
290 return false; # No error
302 $mainConfig = MediaWikiServices::getInstance()->getMainConfig();
303 $sharpenReductionThreshold = $mainConfig->get( MainConfigNames::SharpenReductionThreshold );
304 $sharpenParameter = $mainConfig->get( MainConfigNames::SharpenParameter );
305 $maxAnimatedGifArea = $mainConfig->get( MainConfigNames::MaxAnimatedGifArea );
306 $jpegPixelFormat = $mainConfig->get( MainConfigNames::JpegPixelFormat );
307 $jpegQuality = $mainConfig->get( MainConfigNames::JpegQuality );
310 $im->readImage( $params[
'srcPath'] );
312 if ( $params[
'mimeType'] ===
'image/jpeg' ) {
314 if ( ( $params[
'physicalWidth'] + $params[
'physicalHeight'] )
315 / ( $params[
'srcWidth'] + $params[
'srcHeight'] )
316 < $sharpenReductionThreshold
319 [ $radius, $sigma ] = explode(
'x', $sharpenParameter, 2 );
320 $im->sharpenImage( (
float)$radius, (
float)$sigma );
322 $qualityVal = isset( $params[
'quality'] ) ? (string)$params[
'quality'] :
null;
323 $im->setCompressionQuality( $qualityVal ?: $jpegQuality );
324 if ( $params[
'interlace'] ) {
325 $im->setInterlaceScheme( Imagick::INTERLACE_JPEG );
327 if ( $jpegPixelFormat ) {
329 $im->setSamplingFactors( $factors );
331 } elseif ( $params[
'mimeType'] ===
'image/png' ) {
332 $im->setCompressionQuality( 95 );
333 if ( $params[
'interlace'] ) {
334 $im->setInterlaceScheme( Imagick::INTERLACE_PNG );
336 } elseif ( $params[
'mimeType'] ===
'image/gif' ) {
337 if ( $this->
getImageArea( $image ) > $maxAnimatedGifArea ) {
340 $im->setImageScene( 0 );
343 $im = $im->coalesceImages();
346 if ( $params[
'interlace'] ) {
347 $im->setInterlaceScheme( Imagick::INTERLACE_GIF );
351 $rotation = isset( $params[
'disableRotation'] ) ? 0 : $this->
getRotation( $image );
354 $im->setImageBackgroundColor(
new ImagickPixel(
'white' ) );
357 foreach ( $im as $i => $frame ) {
358 if ( !$frame->thumbnailImage( $width, $height,
false ) ) {
362 $im->setImageDepth( 8 );
364 if ( $rotation && !$im->rotateImage(
new ImagickPixel(
'white' ), 360 - $rotation ) ) {
369 wfDebug( __METHOD__ .
": Writing animated thumbnail" );
371 $result = $im->writeImages( $params[
'dstPath'],
true );
373 $result = $im->writeImage( $params[
'dstPath'] );
377 "Unable to write thumbnail to {$params['dstPath']}" );
379 }
catch ( ImagickException $e ) {
396 $customConvertCommand = MediaWikiServices::getInstance()->getMainConfig()
397 ->get( MainConfigNames::CustomConvertCommand );
401 $cmd = strtr( $customConvertCommand, [
402 '%s' => Shell::escape( $params[
'srcPath'] ),
403 '%d' => Shell::escape( $params[
'dstPath'] ),
404 '%w' => Shell::escape( $params[
'physicalWidth'] ),
405 '%h' => Shell::escape( $params[
'physicalHeight'] ),
407 wfDebug( __METHOD__ .
": Running custom convert command $cmd" );
411 if ( $retval !== 0 ) {
417 return false; # No error
429 # Use PHP's builtin GD library functions.
430 # First find out what kind of file this is, and select the correct
431 # input routine for this.
434 'image/gif' => [
'imagecreatefromgif',
'palette',
false,
'imagegif' ],
435 'image/jpeg' => [
'imagecreatefromjpeg',
'truecolor',
true,
436 [ __CLASS__,
'imageJpegWrapper' ] ],
437 'image/png' => [
'imagecreatefrompng',
'bits',
false,
'imagepng' ],
438 'image/vnd.wap.wbmp' => [
'imagecreatefromwbmp',
'palette',
false,
'imagewbmp' ],
439 'image/xbm' => [
'imagecreatefromxbm',
'palette',
false,
'imagexbm' ],
442 if ( !isset( $typemap[$params[
'mimeType']] ) ) {
443 $err =
'Image type not supported';
445 $errMsg =
wfMessage(
'thumbnail_image-type' )->text();
449 [ $loader, $colorStyle, $useQuality, $saveType ] = $typemap[$params[
'mimeType']];
451 if ( !function_exists( $loader ) ) {
452 $err =
"Incomplete GD library configuration: missing function $loader";
454 $errMsg =
wfMessage(
'thumbnail_gd-library', $loader )->text();
459 if ( !file_exists( $params[
'srcPath'] ) ) {
460 $err =
"File seems to be missing: {$params['srcPath']}";
462 $errMsg =
wfMessage(
'thumbnail_image-missing', $params[
'srcPath'] )->text();
467 if ( filesize( $params[
'srcPath'] ) === 0 ) {
468 $err =
"Image file size seems to be zero.";
470 $errMsg =
wfMessage(
'thumbnail_image-size-zero', $params[
'srcPath'] )->text();
475 $src_image = $loader( $params[
'srcPath'] );
477 $rotation = function_exists(
'imagerotate' ) && !isset( $params[
'disableRotation'] ) ?
481 $dst_image = imagecreatetruecolor( $width, $height );
485 $background = imagecolorallocate( $dst_image, 0, 0, 0 );
486 imagecolortransparent( $dst_image, $background );
487 imagealphablending( $dst_image,
false );
489 if ( $colorStyle ===
'palette' ) {
492 imagecopyresized( $dst_image, $src_image,
495 imagesx( $src_image ), imagesy( $src_image ) );
497 imagecopyresampled( $dst_image, $src_image,
500 imagesx( $src_image ), imagesy( $src_image ) );
503 if ( $rotation % 360 !== 0 && $rotation % 90 === 0 ) {
504 $rot_image = imagerotate( $dst_image, $rotation, 0 );
505 imagedestroy( $dst_image );
506 $dst_image = $rot_image;
509 imagesavealpha( $dst_image,
true );
511 $funcParams = [ $dst_image, $params[
'dstPath'] ];
512 if ( $useQuality && isset( $params[
'quality'] ) ) {
513 $funcParams[] = $params[
'quality'];
516 $saveType( ...$funcParams );
518 imagedestroy( $dst_image );
519 imagedestroy( $src_image );
521 return false; # No error
534 $jpegQuality = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::JpegQuality );
536 imageinterlace( $dst_image );
537 imagejpeg( $dst_image, $thumbPath, $quality ?? $jpegQuality );
550 # ImageMagick supports autorotation
553 # Imagick::rotateImage
556 # GD's imagerotate function is used to rotate images, but not
557 # all precompiled PHP versions have that function
558 return function_exists(
'imagerotate' );
560 # Other scalers don't support rotation
571 $enableAutoRotation = MediaWikiServices::getInstance()->getMainConfig()
572 ->get( MainConfigNames::EnableAutoRotation );
574 if ( $enableAutoRotation ===
null ) {
579 return $enableAutoRotation;
590 public function rotate( $file, $params ) {
591 $imageMagickConvertCommand = MediaWikiServices::getInstance()
592 ->getMainConfig()->get( MainConfigNames::ImageMagickConvertCommand );
594 $rotation = ( $params[
'rotation'] + $this->
getRotation( $file ) ) % 360;
600 $cmd = Shell::escape( $imageMagickConvertCommand ) .
" " .
602 " -rotate " . Shell::escape(
"-$rotation" ) .
" " .
604 wfDebug( __METHOD__ .
": running ImageMagick: $cmd" );
607 if ( $retval !== 0 ) {
616 $im->readImage( $params[
'srcPath'] );
617 if ( !$im->rotateImage(
new ImagickPixel(
'white' ), 360 - $rotation ) ) {
619 "Error rotating $rotation degrees" );
621 $result = $im->writeImage( $params[
'dstPath'] );
624 "Unable to write image to {$params['dstPath']}" );
630 "$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.
hasGDSupport()
Whether the php-gd extension supports this type of file.
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.