19use UnexpectedValueException;
43 if ( !$dstPath && $checkDstPath ) {
44 # No output path available, client side scaling only
46 } elseif ( !$useImageResize ) {
48 } elseif ( $useImageMagick ) {
50 } elseif ( $customConvertCommand ) {
52 } elseif ( $this->
hasGDSupport() && function_exists(
'imagecreatetruecolor' ) ) {
54 } elseif ( class_exists(
'Imagick' ) ) {
78 $res = parent::makeParamString( $params );
79 if ( isset( $params[
'interlace'] ) && $params[
'interlace'] ) {
80 return "interlaced-$res";
90 $remainder = preg_replace(
'/^interlaced-/',
'', $str );
91 $params = parent::parseParamString( $remainder );
92 if ( $params ===
false ) {
95 $params[
'interlace'] = $str !== $remainder;
104 if ( $name ===
'interlace' ) {
105 return $value ===
false || $value ===
true;
107 return parent::validateParam( $name, $value );
119 if ( !parent::normaliseParams( $image, $params ) ) {
122 $mimeType = $image->getMimeType();
123 $interlace = isset( $params[
'interlace'] ) && $params[
'interlace']
124 && isset( $maxInterlacingAreas[$mimeType] )
125 && $this->
getImageArea( $image ) <= $maxInterlacingAreas[$mimeType];
126 $params[
'interlace'] = $interlace;
137 switch ( $pixelFormat ) {
139 return [
'1x1',
'1x1',
'1x1' ];
141 return [
'2x1',
'1x1',
'1x1' ];
143 return [
'2x2',
'1x1',
'1x1' ];
145 throw new UnexpectedValueException(
'Invalid pixel format for JPEG output' );
172 $animation_post = [];
176 if ( $params[
'mimeType'] ===
'image/jpeg' ) {
177 $qualityVal = isset( $params[
'quality'] ) ? (string)$params[
'quality'] :
null;
178 $quality = [
'-quality', $qualityVal ?: (string)$jpegQuality ];
179 if ( $params[
'interlace'] ) {
180 $animation_post = [
'-interlace',
'JPEG' ];
182 # Sharpening, see T8193
183 if ( ( $params[
'physicalWidth'] + $params[
'physicalHeight'] )
184 / ( $params[
'srcWidth'] + $params[
'srcHeight'] )
185 < $sharpenReductionThreshold
187 $sharpen = [
'-sharpen', $sharpenParameter ];
191 $decoderHint = [
'-define',
"jpeg:size={$params['physicalDimensions']}" ];
193 if ( $jpegPixelFormat ) {
195 $subsampling = [
'-sampling-factor', implode(
',', $factors ) ];
197 } elseif ( $params[
'mimeType'] ===
'image/png' ) {
198 $quality = [
'-quality',
'95' ];
199 if ( $params[
'interlace'] ) {
200 $animation_post = [
'-interlace',
'PNG' ];
202 } elseif ( $params[
'mimeType'] ===
'image/webp' ) {
203 $quality = [
'-quality',
'95' ];
204 } elseif ( $params[
'mimeType'] ===
'image/gif' ) {
205 if ( $this->
getImageArea( $image ) > $maxAnimatedGifArea ) {
211 $animation_pre = [
'-coalesce' ];
215 $animation_post = [
'-fuzz',
'5%',
'-layers',
'optimizeTransparency' ];
220 $animation_post[] =
'-interlace';
221 $animation_post[] =
'GIF';
223 } elseif ( $params[
'mimeType'] ===
'image/x-xcf' ) {
231 '-background',
'transparent',
233 '-background',
'white',
238 $env = [
'OMP_NUM_THREADS' => 1 ];
239 if ( (
string)$imageMagickTempDir !==
'' ) {
240 $env[
'MAGICK_TMPDIR'] = $imageMagickTempDir;
243 $rotation = isset( $params[
'disableRotation'] ) ? 0 : $this->
getRotation( $image );
246 $cmd = Shell::escape( ...array_merge(
247 [ $imageMagickConvertCommand ],
251 [
'-background',
'white' ],
258 [
'-thumbnail',
"{$width}x{$height}!" ],
260 ( $params[
'comment'] !==
''
264 [
'+set',
'Thumb::URI' ],
267 [
'-rotate',
"-$rotation" ],
272 wfDebug( __METHOD__ .
": running ImageMagick: $cmd" );
276 if ( $retval !== 0 ) {
282 return false; # No error
302 $im->readImage( $params[
'srcPath'] );
304 if ( $params[
'mimeType'] ===
'image/jpeg' ) {
306 if ( ( $params[
'physicalWidth'] + $params[
'physicalHeight'] )
307 / ( $params[
'srcWidth'] + $params[
'srcHeight'] )
308 < $sharpenReductionThreshold
311 [ $radius, $sigma ] = explode(
'x', $sharpenParameter, 2 );
312 $im->sharpenImage( (
float)$radius, (
float)$sigma );
314 $qualityVal = isset( $params[
'quality'] ) ? (int)$params[
'quality'] :
null;
315 $im->setCompressionQuality( $qualityVal ?: $jpegQuality );
316 if ( $params[
'interlace'] ) {
317 $im->setInterlaceScheme( Imagick::INTERLACE_JPEG );
319 if ( $jpegPixelFormat ) {
321 $im->setSamplingFactors( $factors );
323 } elseif ( $params[
'mimeType'] ===
'image/png' ) {
324 $im->setCompressionQuality( 95 );
325 if ( $params[
'interlace'] ) {
326 $im->setInterlaceScheme( Imagick::INTERLACE_PNG );
328 } elseif ( $params[
'mimeType'] ===
'image/gif' ) {
329 if ( $this->
getImageArea( $image ) > $maxAnimatedGifArea ) {
332 $im->setImageScene( 0 );
335 $im = $im->coalesceImages();
338 if ( $params[
'interlace'] ) {
339 $im->setInterlaceScheme( Imagick::INTERLACE_GIF );
343 $rotation = isset( $params[
'disableRotation'] ) ? 0 : $this->
getRotation( $image );
346 $im->setImageBackgroundColor(
new ImagickPixel(
'white' ) );
349 foreach ( $im as $i => $frame ) {
350 if ( !$frame->thumbnailImage( $width, $height,
false ) ) {
354 $im->setImageDepth( 8 );
356 if ( $rotation && !$im->rotateImage(
new ImagickPixel(
'white' ), 360 - $rotation ) ) {
361 wfDebug( __METHOD__ .
": Writing animated thumbnail" );
363 $result = $im->writeImages( $params[
'dstPath'],
true );
365 $result = $im->writeImage( $params[
'dstPath'] );
369 "Unable to write thumbnail to {$params['dstPath']}" );
371 }
catch ( ImagickException $e ) {
393 $cmd = strtr( $customConvertCommand, [
394 '%s' => Shell::escape( $params[
'srcPath'] ),
395 '%d' => Shell::escape( $params[
'dstPath'] ),
396 '%w' => Shell::escape( $params[
'physicalWidth'] ),
397 '%h' => Shell::escape( $params[
'physicalHeight'] ),
399 wfDebug( __METHOD__ .
": Running custom convert command $cmd" );
403 if ( $retval !== 0 ) {
409 return false; # No error
421 # Use PHP's builtin GD library functions.
422 # First find out what kind of file this is, and select the correct
423 # input routine for this.
426 'image/gif' => [
'imagecreatefromgif',
'palette',
false,
'imagegif' ],
427 'image/jpeg' => [
'imagecreatefromjpeg',
'truecolor',
true,
428 [ self::class,
'imageJpegWrapper' ] ],
429 'image/png' => [
'imagecreatefrompng',
'bits',
false,
'imagepng' ],
430 'image/vnd.wap.wbmp' => [
'imagecreatefromwbmp',
'palette',
false,
'imagewbmp' ],
431 'image/xbm' => [
'imagecreatefromxbm',
'palette',
false,
'imagexbm' ],
434 if ( !isset( $typemap[$params[
'mimeType']] ) ) {
435 $err =
'Image type not supported';
437 $errMsg =
wfMessage(
'thumbnail_image-type' )->text();
441 [ $loader, $colorStyle, $useQuality, $saveType ] = $typemap[$params[
'mimeType']];
443 if ( !function_exists( $loader ) ) {
444 $err =
"Incomplete GD library configuration: missing function $loader";
446 $errMsg =
wfMessage(
'thumbnail_gd-library', $loader )->text();
451 if ( !file_exists( $params[
'srcPath'] ) ) {
452 $err =
"File seems to be missing: {$params['srcPath']}";
454 $errMsg =
wfMessage(
'thumbnail_image-missing', $params[
'srcPath'] )->text();
459 if ( filesize( $params[
'srcPath'] ) === 0 ) {
460 $err =
"Image file size seems to be zero.";
462 $errMsg =
wfMessage(
'thumbnail_image-size-zero', $params[
'srcPath'] )->text();
467 $src_image = $loader( $params[
'srcPath'] );
469 $rotation = function_exists(
'imagerotate' ) && !isset( $params[
'disableRotation'] ) ?
473 $dst_image = imagecreatetruecolor( $width, $height );
477 $background = imagecolorallocate( $dst_image, 0, 0, 0 );
478 imagecolortransparent( $dst_image, $background );
479 imagealphablending( $dst_image,
false );
481 if ( $colorStyle ===
'palette' ) {
484 imagecopyresized( $dst_image, $src_image,
487 imagesx( $src_image ), imagesy( $src_image ) );
489 imagecopyresampled( $dst_image, $src_image,
492 imagesx( $src_image ), imagesy( $src_image ) );
495 if ( $rotation % 360 !== 0 && $rotation % 90 === 0 ) {
496 $rot_image = imagerotate( $dst_image, $rotation, 0 );
497 imagedestroy( $dst_image );
498 $dst_image = $rot_image;
501 imagesavealpha( $dst_image,
true );
503 $funcParams = [ $dst_image, $params[
'dstPath'] ];
504 if ( $useQuality && isset( $params[
'quality'] ) ) {
505 $funcParams[] = $params[
'quality'];
508 $saveType( ...$funcParams );
510 imagedestroy( $dst_image );
511 imagedestroy( $src_image );
513 return false; # No error
528 imageinterlace( $dst_image );
529 imagejpeg( $dst_image, $thumbPath, $quality ?? $jpegQuality );
542 # ImageMagick supports autorotation
545 # Imagick::rotateImage
548 # GD's imagerotate function is used to rotate images, but not
549 # all precompiled PHP versions have that function
550 return function_exists(
'imagerotate' );
552 # Other scalers don't support rotation
566 if ( $enableAutoRotation ===
null ) {
571 return $enableAutoRotation;
582 public function rotate( $file, $params ) {
586 $rotation = ( $params[
'rotation'] + $this->
getRotation( $file ) ) % 360;
592 $cmd = Shell::escape( $imageMagickConvertCommand ) .
" " .
594 " -rotate " . Shell::escape(
"-$rotation" ) .
" " .
596 wfDebug( __METHOD__ .
": running ImageMagick: $cmd" );
599 if ( $retval !== 0 ) {
608 $im->readImage( $params[
'srcPath'] );
609 if ( !$im->rotateImage(
new ImagickPixel(
'white' ), 360 - $rotation ) ) {
611 "Error rotating $rotation degrees" );
613 $result = $im->writeImage( $params[
'dstPath'] );
616 "Unable to write image to {$params['dstPath']}" );
622 "$scaler rotation not implemented" );
628class_alias( BitmapHandler::class,
'BitmapHandler' );
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.
A class containing constants representing the names of configuration variables.
const JpegPixelFormat
Name constant for the JpegPixelFormat setting, for use with Config::get()
const UseImageMagick
Name constant for the UseImageMagick setting, for use with Config::get()
const ImageMagickTempDir
Name constant for the ImageMagickTempDir setting, for use with Config::get()
const ImageMagickConvertCommand
Name constant for the ImageMagickConvertCommand setting, for use with Config::get()
const UseImageResize
Name constant for the UseImageResize setting, for use with Config::get()
const MaxInterlacingAreas
Name constant for the MaxInterlacingAreas setting, for use with Config::get()
const CustomConvertCommand
Name constant for the CustomConvertCommand setting, for use with Config::get()
const JpegQuality
Name constant for the JpegQuality setting, for use with Config::get()
const EnableAutoRotation
Name constant for the EnableAutoRotation setting, for use with Config::get()
const SharpenParameter
Name constant for the SharpenParameter setting, for use with Config::get()
const SharpenReductionThreshold
Name constant for the SharpenReductionThreshold setting, for use with Config::get()
const MaxAnimatedGifArea
Name constant for the MaxAnimatedGifArea setting, for use with Config::get()