Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
16.67% covered (danger)
16.67%
31 / 186
0.00% covered (danger)
0.00%
0 / 20
CRAP
0.00% covered (danger)
0.00%
0 / 1
TransformationalImageHandler
16.76% covered (danger)
16.76%
31 / 185
0.00% covered (danger)
0.00%
0 / 20
3232.71
0.00% covered (danger)
0.00%
0 / 1
 normaliseParams
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
20
 extractPreRotationDimensions
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 doTransform
32.63% covered (danger)
32.63%
31 / 95
0.00% covered (danger)
0.00%
0 / 1
365.96
 getThumbnailSource
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getScalerType
n/a
0 / 0
n/a
0 / 0
0
 getClientScalingThumbnailImage
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 transformImageMagick
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 transformImageMagickExt
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 transformCustom
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getMediaTransformError
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 transformGd
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 escapeMagickProperty
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
20
 escapeMagickInput
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 escapeMagickOutput
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 escapeMagickPath
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
42
 getMagickVersion
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
6
 canRotate
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 autoRotateEnabled
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 rotate
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 mustRender
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
 isImageAreaOkForThumbnaling
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
42
1<?php
2/**
3 * Base class for handlers which require transforming images in a
4 * similar way as BitmapHandler does.
5 *
6 * This was split from BitmapHandler on the basis that some extensions
7 * might want to work in a similar way to BitmapHandler, but for
8 * different formats.
9 *
10 * @license GPL-2.0-or-later
11 * @file
12 * @ingroup Media
13 */
14
15namespace MediaWiki\Media;
16
17use InvalidArgumentException;
18use MediaWiki\FileRepo\File\File;
19use MediaWiki\HookContainer\HookRunner;
20use MediaWiki\MainConfigNames;
21use MediaWiki\MediaWikiServices;
22use MediaWiki\Shell\Shell;
23
24/**
25 * Handler for images that need to be transformed
26 *
27 * @stable to extend
28 *
29 * @since 1.24
30 * @ingroup Media
31 */
32abstract class TransformationalImageHandler extends ImageHandler {
33    /**
34     * @stable to override
35     * @param File $image
36     * @param array &$params Transform parameters. Entries with the keys 'width'
37     * and 'height' are the respective screen width and height, while the keys
38     * 'physicalWidth' and 'physicalHeight' indicate the thumbnail dimensions.
39     * @return bool
40     */
41    public function normaliseParams( $image, &$params ) {
42        if ( !parent::normaliseParams( $image, $params ) ) {
43            return false;
44        }
45
46        # Obtain the source, pre-rotation dimensions
47        $srcWidth = $image->getWidth( $params['page'] );
48        $srcHeight = $image->getHeight( $params['page'] );
49
50        $params['physicalWidth'] = $this->getSteppedThumbWidth(
51            $image, $params['physicalWidth'], $srcWidth, $srcHeight
52        );
53        $params['physicalHeight'] = File::scaleHeight( $srcWidth, $srcHeight, $params['physicalWidth'] );
54
55        # Don't make an image bigger than the source
56        if ( $params['physicalWidth'] >= $srcWidth ) {
57            $params['physicalWidth'] = $srcWidth;
58            $params['physicalHeight'] = $srcHeight;
59
60            # Skip scaling limit checks if no scaling is required
61            # due to requested size being bigger than source.
62            if ( !$image->mustRender() ) {
63                return true;
64            }
65        }
66
67        return true;
68    }
69
70    /**
71     * Extracts the width/height if the image will be scaled before rotating
72     *
73     * This will match the physical size/aspect ratio of the original image
74     * prior to application of the rotation -- so for a portrait image that's
75     * stored as raw landscape with 90-degrees rotation, the resulting size
76     * will be wider than it is tall.
77     *
78     * @param array $params Parameters as returned by normaliseParams
79     * @param int $rotation The rotation angle that will be applied
80     * @return array ($width, $height) array
81     */
82    public function extractPreRotationDimensions( $params, $rotation ) {
83        if ( $rotation === 90 || $rotation === 270 ) {
84            // We'll resize before rotation, so swap the dimensions again
85            $width = $params['physicalHeight'];
86            $height = $params['physicalWidth'];
87        } else {
88            $width = $params['physicalWidth'];
89            $height = $params['physicalHeight'];
90        }
91
92        return [ $width, $height ];
93    }
94
95    /**
96     * Create a thumbnail.
97     *
98     * This sets up various parameters, and then calls a helper method
99     * based on $this->getScalerType in order to scale the image.
100     * @stable to override
101     *
102     * @param File $image
103     * @param string $dstPath
104     * @param string $dstUrl
105     * @param array $params
106     * @param int $flags
107     * @return MediaTransformError|ThumbnailImage|TransformParameterError
108     */
109    public function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 ) {
110        if ( !$this->normaliseParams( $image, $params ) ) {
111            return new TransformParameterError( $params );
112        }
113
114        // Create a parameter array to pass to the scaler
115        $scalerParams = [
116            // The size to which the image will be resized
117            'physicalWidth' => $params['physicalWidth'],
118            'physicalHeight' => $params['physicalHeight'],
119            'physicalDimensions' => "{$params['physicalWidth']}x{$params['physicalHeight']}",
120            // The size of the image on the page
121            'clientWidth' => $params['width'],
122            'clientHeight' => $params['height'],
123            // Comment as will be added to the Exif of the thumbnail
124            'comment' => isset( $params['descriptionUrl'] )
125                ? "File source: {$params['descriptionUrl']}"
126                : '',
127            // Properties of the original image
128            'srcWidth' => $image->getWidth(),
129            'srcHeight' => $image->getHeight(),
130            'mimeType' => $image->getMimeType(),
131            'dstPath' => $dstPath,
132            'dstUrl' => $dstUrl,
133            'interlace' => $params['interlace'] ?? false,
134        ];
135
136        if ( isset( $params['quality'] ) && $params['quality'] === 'low' ) {
137            $scalerParams['quality'] = 30;
138        }
139
140        // For subclasses that might be paged.
141        if ( $image->isMultipage() && isset( $params['page'] ) ) {
142            $scalerParams['page'] = (int)$params['page'];
143        }
144
145        # Determine scaler type
146        $scaler = $this->getScalerType( $dstPath );
147
148        if ( is_array( $scaler ) ) {
149            $scalerName = get_class( $scaler[0] );
150        } else {
151            $scalerName = $scaler;
152        }
153
154        wfDebug( __METHOD__ . ": creating {$scalerParams['physicalDimensions']} " .
155            "thumbnail of {$image->getPath()} at $dstPath using scaler $scalerName" );
156
157        if ( !$image->mustRender() &&
158            $scalerParams['physicalWidth'] == $scalerParams['srcWidth']
159            && $scalerParams['physicalHeight'] == $scalerParams['srcHeight']
160            && !isset( $scalerParams['quality'] )
161        ) {
162            # normaliseParams (or the user) wants us to return the unscaled image
163            wfDebug( __METHOD__ . ": returning unscaled image" );
164
165            return $this->getClientScalingThumbnailImage( $image, $params );
166        }
167
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
171            return $this->getClientScalingThumbnailImage( $image, $params );
172        }
173
174        if ( $image->isTransformedLocally() && !$this->isImageAreaOkForThumbnaling( $image, $params ) ) {
175            $maxImageArea = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::MaxImageArea );
176            return new TransformTooBigImageAreaError( $params, $maxImageArea );
177        }
178
179        if ( $flags & self::TRANSFORM_LATER ) {
180            wfDebug( __METHOD__ . ": Transforming later per flags." );
181            return new ThumbnailImage( $image, $dstUrl, false, $params );
182        }
183
184        # Try to make a target path for the thumbnail
185        if ( !wfMkdirParents( dirname( $dstPath ), null, __METHOD__ ) ) {
186            wfDebug( __METHOD__ . ": Unable to create thumbnail destination " .
187                "directory, falling back to client scaling" );
188
189            return $this->getClientScalingThumbnailImage( $image, $params );
190        }
191
192        # Transform functions and binaries need a FS source file
193        $thumbnailSource = $this->getThumbnailSource( $image, $params );
194
195        // If the source isn't the original, disable EXIF rotation because it's already been applied
196        if ( $scalerParams['srcWidth'] != $thumbnailSource['width']
197            || $scalerParams['srcHeight'] != $thumbnailSource['height'] ) {
198            $scalerParams['disableRotation'] = true;
199        }
200
201        $scalerParams['srcPath'] = $thumbnailSource['path'];
202        $scalerParams['srcWidth'] = $thumbnailSource['width'];
203        $scalerParams['srcHeight'] = $thumbnailSource['height'];
204
205        if ( $scalerParams['srcPath'] === false ) { // Failed to get local copy
206            wfDebugLog( 'thumbnail',
207                sprintf( 'Thumbnail failed on %s: could not get local copy of "%s"',
208                    wfHostname(), $image->getName() ) );
209
210            return new MediaTransformError( 'thumbnail_error',
211                $scalerParams['clientWidth'], $scalerParams['clientHeight'],
212                wfMessage( 'filemissing' )
213            );
214        }
215
216        // Try a hook. Called "Bitmap" for historical reasons.
217        /** @var MediaTransformOutput $mto */
218        $mto = null;
219        ( new HookRunner( MediaWikiServices::getInstance()->getHookContainer() ) )
220            ->onBitmapHandlerTransform( $this, $image, $scalerParams, $mto );
221        if ( $mto !== null ) {
222            wfDebug( __METHOD__ . ": Hook to BitmapHandlerTransform created an mto" );
223            $scaler = 'hookaborted';
224        }
225
226        // $scaler will return a MediaTransformError on failure, or false on success.
227        // If the scaler is successful, it will have created a thumbnail at the destination
228        // path.
229        if ( is_array( $scaler ) && is_callable( $scaler ) ) {
230            // Allow subclasses to specify their own rendering methods.
231            $err = $scaler( $image, $scalerParams );
232        } else {
233            switch ( $scaler ) {
234                case 'hookaborted':
235                    # Handled by the hook above
236                    $err = $mto->isError() ? $mto : false;
237                    break;
238                case 'im':
239                    $err = $this->transformImageMagick( $image, $scalerParams );
240                    break;
241                case 'custom':
242                    $err = $this->transformCustom( $image, $scalerParams );
243                    break;
244                case 'imext':
245                    $err = $this->transformImageMagickExt( $image, $scalerParams );
246                    break;
247                case 'gd':
248                default:
249                    $err = $this->transformGd( $image, $scalerParams );
250                    break;
251            }
252        }
253
254        // Remove the file if a zero-byte thumbnail was created, or if there was an error
255        // @phan-suppress-next-line PhanTypeMismatchArgument Relaying on bool/int conversion to cast objects correct
256        $removed = $this->removeBadFile( $dstPath, (bool)$err );
257        if ( $err ) {
258            // transform returned MediaTransformError
259            return $err;
260        }
261
262        if ( $removed ) {
263            // Thumbnail was zero-byte and had to be removed
264            return new MediaTransformError( 'thumbnail_error',
265                $scalerParams['clientWidth'], $scalerParams['clientHeight'],
266                wfMessage( 'unknown-error' )
267            );
268        }
269
270        if ( $mto ) {
271            // @phan-suppress-next-line PhanTypeMismatchReturnSuperType
272            return $mto;
273        }
274
275        return new ThumbnailImage( $image, $dstUrl, $dstPath, $params );
276    }
277
278    /**
279     * Get the source file for the transform
280     *
281     * @param File $file
282     * @param array $params
283     * @return array Array with keys  width, height and path.
284     */
285    protected function getThumbnailSource( $file, $params ) {
286        return $file->getThumbnailSource( $params );
287    }
288
289    /**
290     * Returns what sort of scaler type should be used.
291     *
292     * Values can be one of client, im, custom, gd, imext, or an array
293     * of object, method-name to call that specific method.
294     *
295     * If specifying a custom scaler command with [ Obj, method ],
296     * the method in question should take 2 parameters, a File object,
297     * and a $scalerParams array with various options (See doTransform
298     * for what is in $scalerParams). On error it should return a
299     * MediaTransformError object. On success it should return false,
300     * and simply make sure the thumbnail file is located at
301     * $scalerParams['dstPath'].
302     *
303     * If there is a problem with the output path, it returns "client"
304     * to do client side scaling.
305     *
306     * @param string|null $dstPath
307     * @param bool $checkDstPath Check that $dstPath is valid
308     * @return string|callable One of client, im, custom, gd, imext, or a callable
309     */
310    abstract protected function getScalerType( $dstPath, $checkDstPath = true );
311
312    /**
313     * Get a ThumbnailImage that represents an image that will be scaled
314     * client side
315     *
316     * @stable to override
317     * @param File $image File associated with this thumbnail
318     * @param array $params Media handler parameters
319     * @return ThumbnailImage
320     *
321     * @todo FIXME: No rotation support
322     */
323    protected function getClientScalingThumbnailImage( $image, $params ) {
324        $url = $image->modifyClientThumbUrl( $image->getUrl(), $params );
325        return new ThumbnailImage( $image, $url, null, $params );
326    }
327
328    /**
329     * Transform an image using ImageMagick
330     *
331     * This is a stub method. The real method is in BitmapHandler.
332     *
333     * @stable to override
334     * @param File $image File associated with this thumbnail
335     * @param array $params Array with scaler params
336     *
337     * @return MediaTransformError|false Error object if error occurred, false (=no error) otherwise
338     */
339    protected function transformImageMagick( $image, $params ) {
340        return $this->getMediaTransformError( $params, "Unimplemented" );
341    }
342
343    /**
344     * Transform an image using the Imagick PHP extension
345     *
346     * This is a stub method. The real method is in BitmapHandler.
347     *
348     * @stable to override
349     * @param File $image File associated with this thumbnail
350     * @param array $params Array with scaler params
351     *
352     * @return MediaTransformError Error object if error occurred, false (=no error) otherwise
353     */
354    protected function transformImageMagickExt( $image, $params ) {
355        return $this->getMediaTransformError( $params, "Unimplemented" );
356    }
357
358    /**
359     * Transform an image using a custom command
360     *
361     * This is a stub method. The real method is in BitmapHandler.
362     *
363     * @stable to override
364     * @param File $image File associated with this thumbnail
365     * @param array $params Array with scaler params
366     *
367     * @return MediaTransformError Error object if error occurred, false (=no error) otherwise
368     */
369    protected function transformCustom( $image, $params ) {
370        return $this->getMediaTransformError( $params, "Unimplemented" );
371    }
372
373    /**
374     * Get a MediaTransformError with error 'thumbnail_error'
375     *
376     * @param array $params Parameter array as passed to the transform* functions
377     * @param string $errMsg Error message
378     * @return MediaTransformError
379     */
380    public function getMediaTransformError( $params, $errMsg ) {
381        return new MediaTransformError( 'thumbnail_error', $params['clientWidth'],
382            $params['clientHeight'], $errMsg );
383    }
384
385    /**
386     * Transform an image using the built in GD library
387     *
388     * This is a stub method. The real method is in BitmapHandler.
389     *
390     * @param File $image File associated with this thumbnail
391     * @param array $params Array with scaler params
392     *
393     * @return MediaTransformError Error object if error occurred, false (=no error) otherwise
394     */
395    protected function transformGd( $image, $params ) {
396        return $this->getMediaTransformError( $params, "Unimplemented" );
397    }
398
399    /**
400     * Escape a string for ImageMagick's property input (e.g. -set -comment)
401     * See InterpretImageProperties() in magick/property.c
402     * @param string $s
403     * @return string
404     */
405    protected function escapeMagickProperty( $s ) {
406        // Double the backslashes
407        $s = str_replace( '\\', '\\\\', $s );
408        // Double the percents
409        $s = str_replace( '%', '%%', $s );
410        // Escape initial - or @
411        if ( strlen( $s ) > 0 && ( $s[0] === '-' || $s[0] === '@' ) ) {
412            $s = '\\' . $s;
413        }
414
415        return $s;
416    }
417
418    /**
419     * Escape a string for ImageMagick's input filenames. See ExpandFilenames()
420     * and GetPathComponent() in magick/utility.c.
421     *
422     * This won't work with an initial ~ or @, so input files should be prefixed
423     * with the directory name.
424     *
425     * Glob character unescaping is broken in ImageMagick before 6.6.1-5, but
426     * it's broken in a way that doesn't involve trying to convert every file
427     * in a directory, so we're better off escaping and waiting for the bugfix
428     * to filter down to users.
429     *
430     * @param string $path The file path
431     * @param string|false $scene The scene specification, or false if there is none
432     * @return string
433     */
434    protected function escapeMagickInput( $path, $scene = false ) {
435        # Die on initial metacharacters (caller should prepend path)
436        $firstChar = substr( $path, 0, 1 );
437        if ( $firstChar === '~' || $firstChar === '@' ) {
438            throw new InvalidArgumentException( __METHOD__ . ': cannot escape this path name' );
439        }
440
441        # Escape glob chars
442        $path = preg_replace( '/[*?\[\]{}]/', '\\\\\0', $path );
443
444        return $this->escapeMagickPath( $path, $scene );
445    }
446
447    /**
448     * Escape a string for ImageMagick's output filename. See
449     * InterpretImageFilename() in magick/image.c.
450     * @param string $path The file path
451     * @param string|false $scene The scene specification, or false if there is none
452     * @return string
453     */
454    protected function escapeMagickOutput( $path, $scene = false ) {
455        $path = str_replace( '%', '%%', $path );
456
457        return $this->escapeMagickPath( $path, $scene );
458    }
459
460    /**
461     * Armour a string against ImageMagick's GetPathComponent(). This is a
462     * helper function for escapeMagickInput() and escapeMagickOutput().
463     *
464     * @param string $path The file path
465     * @param string|false $scene The scene specification, or false if there is none
466     * @return string
467     */
468    protected function escapeMagickPath( $path, $scene = false ) {
469        # Die on format specifiers (other than drive letters). The regex is
470        # meant to match all the formats you get from "convert -list format"
471        if ( preg_match( '/^([a-zA-Z0-9-]+):/', $path, $m ) ) {
472            if ( wfIsWindows() && is_dir( $m[0] ) ) {
473                // OK, it's a drive letter
474                // ImageMagick has a similar exception, see IsMagickConflict()
475            } else {
476                throw new InvalidArgumentException( __METHOD__ . ': unexpected colon character in path name' );
477            }
478        }
479
480        # If there are square brackets, add a do-nothing scene specification
481        # to force a literal interpretation
482        if ( $scene === false ) {
483            if ( str_contains( $path, '[' ) ) {
484                $path .= '[0--1]';
485            }
486        } else {
487            $path .= "[$scene]";
488        }
489
490        return $path;
491    }
492
493    /**
494     * Retrieve the version of the installed ImageMagick
495     * You can use PHPs version_compare() to use this value
496     * Value is cached for one hour.
497     * @return string|false Representing the IM version; false on error
498     */
499    protected function getMagickVersion() {
500        $cache = MediaWikiServices::getInstance()->getLocalServerObjectCache();
501        $method = __METHOD__;
502        return $cache->getWithSetCallback(
503            $cache->makeGlobalKey( 'imagemagick-version' ),
504            $cache::TTL_HOUR,
505            static function () use ( $method ) {
506                $imageMagickConvertCommand = MediaWikiServices::getInstance()
507                    ->getMainConfig()->get( MainConfigNames::ImageMagickConvertCommand );
508
509                $cmd = Shell::escape( $imageMagickConvertCommand ) . ' -version';
510                wfDebug( $method . ": Running convert -version" );
511                $retval = '';
512                $return = wfShellExecWithStderr( $cmd, $retval );
513                $x = preg_match(
514                    '/Version: ImageMagick ([0-9]*\.[0-9]*\.[0-9]*)/', $return, $matches
515                );
516                if ( $x != 1 ) {
517                    wfDebug( $method . ": ImageMagick version check failed" );
518                    return false;
519                }
520
521                return $matches[1];
522            }
523        );
524    }
525
526    /**
527     * Returns whether the current scaler supports rotation.
528     *
529     * @since 1.24 No longer static
530     * @stable to override
531     * @return bool
532     */
533    public function canRotate() {
534        return false;
535    }
536
537    /**
538     * Should we automatically rotate an image based on exif
539     *
540     * @since 1.24 No longer static
541     * @stable to override
542     * @see $wgEnableAutoRotation
543     * @return bool Whether auto rotation is enabled
544     */
545    public function autoRotateEnabled() {
546        return false;
547    }
548
549    /**
550     * Rotate a thumbnail.
551     *
552     * This is a stub. See BitmapHandler::rotate.
553     *
554     * @stable to override
555     * @param File $file
556     * @param array{rotation:int,srcPath:string,dstPath:string} $params Rotate parameters.
557     *   'rotation' clockwise rotation in degrees, allowed are multiples of 90
558     * @since 1.24 Is non-static. From 1.21 it was static
559     * @return MediaTransformError|false
560     */
561    public function rotate( $file, $params ) {
562        return new MediaTransformError( 'thumbnail_error', 0, 0,
563            static::class . ' rotation not implemented' );
564    }
565
566    /**
567     * Returns whether the file needs to be rendered. Returns true if the
568     * file requires rotation and we are able to rotate it.
569     *
570     * @stable to override
571     * @param File $file
572     * @return bool
573     */
574    public function mustRender( $file ) {
575        return $this->canRotate() && $this->getRotation( $file ) != 0;
576    }
577
578    /**
579     * Check if the file is smaller than the maximum image area for thumbnailing.
580     *
581     * Runs the 'BitmapHandlerCheckImageArea' hook.
582     *
583     * @stable to override
584     * @param File $file
585     * @param array &$params
586     * @return bool
587     * @since 1.25
588     */
589    public function isImageAreaOkForThumbnaling( $file, &$params ) {
590        $maxImageArea = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::MaxImageArea );
591
592        # For historical reasons, hook starts with BitmapHandler
593        $checkImageAreaHookResult = null;
594        ( new HookRunner( MediaWikiServices::getInstance()->getHookContainer() ) )->onBitmapHandlerCheckImageArea(
595            $file, $params, $checkImageAreaHookResult );
596
597        if ( $checkImageAreaHookResult !== null ) {
598            // was set by hook, so return that value
599            return (bool)$checkImageAreaHookResult;
600        }
601
602        if ( $maxImageArea === false ) {
603            // Checking is disabled, fine to thumbnail
604            return true;
605        }
606
607        $srcWidth = $file->getWidth( $params['page'] );
608        $srcHeight = $file->getHeight( $params['page'] );
609
610        if ( $srcWidth * $srcHeight > $maxImageArea
611            && !( $file->getMimeType() === 'image/jpeg'
612                && $this->getScalerType( null, false ) === 'im' )
613        ) {
614            # Only ImageMagick can efficiently downsize jpg images without loading
615            # the entire file in memory
616            return false;
617        }
618        return true;
619    }
620}
621
622/** @deprecated class alias since 1.46 */
623class_alias( TransformationalImageHandler::class, 'TransformationalImageHandler' );