17use InvalidArgumentException;
42 if ( !parent::normaliseParams( $image, $params ) ) {
46 # Obtain the source, pre-rotation dimensions
47 $srcWidth = $image->getWidth( $params[
'page'] );
48 $srcHeight = $image->getHeight( $params[
'page'] );
51 $image, $params[
'physicalWidth'], $srcWidth, $srcHeight
53 $params[
'physicalHeight'] = File::scaleHeight( $srcWidth, $srcHeight, $params[
'physicalWidth'] );
55 # Don't make an image bigger than the source
56 if ( $params[
'physicalWidth'] >= $srcWidth ) {
57 $params[
'physicalWidth'] = $srcWidth;
58 $params[
'physicalHeight'] = $srcHeight;
60 # Skip scaling limit checks if no scaling is required
61 # due to requested size being bigger than source.
62 if ( !$image->mustRender() ) {
83 if ( $rotation === 90 || $rotation === 270 ) {
85 $width = $params[
'physicalHeight'];
86 $height = $params[
'physicalWidth'];
88 $width = $params[
'physicalWidth'];
89 $height = $params[
'physicalHeight'];
92 return [ $width, $height ];
109 public function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 ) {
117 'physicalWidth' => $params[
'physicalWidth'],
118 'physicalHeight' => $params[
'physicalHeight'],
119 'physicalDimensions' =>
"{$params['physicalWidth']}x{$params['physicalHeight']}",
121 'clientWidth' => $params[
'width'],
122 'clientHeight' => $params[
'height'],
124 'comment' => isset( $params[
'descriptionUrl'] )
125 ?
"File source: {$params['descriptionUrl']}"
128 'srcWidth' => $image->getWidth(),
129 'srcHeight' => $image->getHeight(),
130 'mimeType' => $image->getMimeType(),
131 'dstPath' => $dstPath,
133 'interlace' => $params[
'interlace'] ??
false,
136 if ( isset( $params[
'quality'] ) && $params[
'quality'] ===
'low' ) {
137 $scalerParams[
'quality'] = 30;
141 if ( $image->isMultipage() && isset( $params[
'page'] ) ) {
142 $scalerParams[
'page'] = (int)$params[
'page'];
145 # Determine scaler type
148 if ( is_array( $scaler ) ) {
149 $scalerName = get_class( $scaler[0] );
151 $scalerName = $scaler;
154 wfDebug( __METHOD__ .
": creating {$scalerParams['physicalDimensions']} " .
155 "thumbnail of {$image->getPath()} at $dstPath using scaler $scalerName" );
157 if ( !$image->mustRender() &&
158 $scalerParams[
'physicalWidth'] == $scalerParams[
'srcWidth']
159 && $scalerParams[
'physicalHeight'] == $scalerParams[
'srcHeight']
160 && !isset( $scalerParams[
'quality'] )
162 # normaliseParams (or the user) wants us to return the unscaled image
163 wfDebug( __METHOD__ .
": returning unscaled image" );
168 if ( $scaler ===
'client' ) {
169 # Client-side image scaling, use the source URL
170 # Using the destination URL in a TRANSFORM_LATER request would be incorrect
174 if ( $image->isTransformedLocally() && !$this->isImageAreaOkForThumbnaling( $image, $params ) ) {
179 if ( $flags & self::TRANSFORM_LATER ) {
180 wfDebug( __METHOD__ .
": Transforming later per flags." );
182 'width' => $scalerParams[
'clientWidth'],
183 'height' => $scalerParams[
'clientHeight']
185 if ( isset( $params[
'quality'] ) ) {
186 $newParams[
'quality'] = $params[
'quality'];
188 if ( isset( $params[
'page'] ) && $params[
'page'] ) {
189 $newParams[
'page'] = $params[
'page'];
194 # Try to make a target path for the thumbnail
196 wfDebug( __METHOD__ .
": Unable to create thumbnail destination " .
197 "directory, falling back to client scaling" );
202 # Transform functions and binaries need a FS source file
206 if ( $scalerParams[
'srcWidth'] != $thumbnailSource[
'width']
207 || $scalerParams[
'srcHeight'] != $thumbnailSource[
'height'] ) {
208 $scalerParams[
'disableRotation'] =
true;
211 $scalerParams[
'srcPath'] = $thumbnailSource[
'path'];
212 $scalerParams[
'srcWidth'] = $thumbnailSource[
'width'];
213 $scalerParams[
'srcHeight'] = $thumbnailSource[
'height'];
215 if ( $scalerParams[
'srcPath'] ===
false ) {
217 sprintf(
'Thumbnail failed on %s: could not get local copy of "%s"',
221 $scalerParams[
'clientWidth'], $scalerParams[
'clientHeight'],
230 ->onBitmapHandlerTransform( $this, $image, $scalerParams, $mto );
231 if ( $mto !==
null ) {
232 wfDebug( __METHOD__ .
": Hook to BitmapHandlerTransform created an mto" );
233 $scaler =
'hookaborted';
239 if ( is_array( $scaler ) && is_callable( $scaler ) ) {
241 $err = $scaler( $image, $scalerParams );
245 # Handled by the hook above
246 $err = $mto->isError() ? $mto :
false;
259 $err = $this->
transformGd( $image, $scalerParams );
275 $scalerParams[
'clientWidth'], $scalerParams[
'clientHeight'],
286 'width' => $scalerParams[
'clientWidth'],
287 'height' => $scalerParams[
'clientHeight']
289 if ( isset( $params[
'quality'] ) ) {
290 $newParams[
'quality'] = $params[
'quality'];
292 if ( isset( $params[
'page'] ) && $params[
'page'] ) {
293 $newParams[
'page'] = $params[
'page'];
295 return new ThumbnailImage( $image, $dstUrl, $dstPath, $newParams );
306 return $file->getThumbnailSource( $params );
330 abstract protected function getScalerType( $dstPath, $checkDstPath =
true );
344 $url = $image->modifyClientThumbUrl( $image->getUrl(), $params );
402 $params[
'clientHeight'], $errMsg );
427 $s = str_replace(
'\\',
'\\\\', $s );
429 $s = str_replace(
'%',
'%%', $s );
431 if ( strlen( $s ) > 0 && ( $s[0] ===
'-' || $s[0] ===
'@' ) ) {
455 # Die on initial metacharacters (caller should prepend path)
456 $firstChar = substr(
$path, 0, 1 );
457 if ( $firstChar ===
'~' || $firstChar ===
'@' ) {
458 throw new InvalidArgumentException( __METHOD__ .
': cannot escape this path name' );
462 $path = preg_replace(
'/[*?\[\]{}]/',
'\\\\\0',
$path );
489 # Die on format specifiers (other than drive letters). The regex is
490 # meant to match all the formats you get from "convert -list format"
491 if ( preg_match(
'/^([a-zA-Z0-9-]+):/',
$path, $m ) ) {
496 throw new InvalidArgumentException( __METHOD__ .
': unexpected colon character in path name' );
500 # If there are square brackets, add a do-nothing scene specification
501 # to force a literal interpretation
502 if ( $scene ===
false ) {
503 if ( str_contains(
$path,
'[' ) ) {
521 $method = __METHOD__;
522 return $cache->getWithSetCallback(
523 $cache->makeGlobalKey(
'imagemagick-version' ),
525 static function () use ( $method ) {
529 $cmd = Shell::escape( $imageMagickConvertCommand ) .
' -version';
530 wfDebug( $method .
": Running convert -version" );
534 '/Version: ImageMagick ([0-9]*\.[0-9]*\.[0-9]*)/', $return,
$matches
537 wfDebug( $method .
": ImageMagick version check failed" );
581 public function rotate( $file, $params ) {
583 static::class .
' rotation not implemented' );
612 # For historical reasons, hook starts with BitmapHandler
613 $checkImageAreaHookResult =
null;
615 $file, $params, $checkImageAreaHookResult );
617 if ( $checkImageAreaHookResult !==
null ) {
619 return (
bool)$checkImageAreaHookResult;
622 if ( $maxImageArea ===
false ) {
627 $srcWidth = $file->getWidth( $params[
'page'] );
628 $srcHeight = $file->getHeight( $params[
'page'] );
630 if ( $srcWidth * $srcHeight > $maxImageArea
631 && !( $file->getMimeType() ===
'image/jpeg'
632 && $this->getScalerType(
null,
false ) ===
'im' )
634 # Only ImageMagick can efficiently downsize jpg images without loading
635 # the entire file in memory
643class_alias( TransformationalImageHandler::class,
'TransformationalImageHandler' );
wfIsWindows()
Check if the operating system is Windows.
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfHostname()
Get host name of the current machine, for use in error reporting.
wfShellExecWithStderr( $cmd, &$retval=null, $environ=[], $limits=[])
Execute a shell command, returning both stdout and stderr.
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not.
wfMkdirParents( $dir, $mode=null, $caller=null)
Make directory, and make all parent directories if they don't exist.
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
A class containing constants representing the names of configuration variables.
const ImageMagickConvertCommand
Name constant for the ImageMagickConvertCommand setting, for use with Config::get()
const MaxImageArea
Name constant for the MaxImageArea setting, for use with Config::get()