vendor/contao/image/src/Resizer.php line 84

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. /*
  4.  * This file is part of Contao.
  5.  *
  6.  * (c) Leo Feyer
  7.  *
  8.  * @license LGPL-3.0-or-later
  9.  */
  10. namespace Contao\Image;
  11. use Imagine\Exception\RuntimeException as ImagineRuntimeException;
  12. use Imagine\Filter\Basic\Autorotate;
  13. use Imagine\Image\Palette\RGB;
  14. use Symfony\Component\Filesystem\Filesystem;
  15. use Webmozart\PathUtil\Path;
  16. class Resizer implements ResizerInterface
  17. {
  18.     /**
  19.      * @var Filesystem
  20.      *
  21.      * @internal
  22.      */
  23.     protected $filesystem;
  24.     /**
  25.      * @var string
  26.      *
  27.      * @internal
  28.      */
  29.     protected $cacheDir;
  30.     /**
  31.      * @var ResizeCalculator
  32.      */
  33.     private $calculator;
  34.     public function __construct(string $cacheDirResizeCalculator $calculator nullFilesystem $filesystem null)
  35.     {
  36.         if (null === $calculator) {
  37.             $calculator = new ResizeCalculator();
  38.         }
  39.         if (null === $filesystem) {
  40.             $filesystem = new Filesystem();
  41.         }
  42.         $this->cacheDir $cacheDir;
  43.         $this->calculator $calculator;
  44.         $this->filesystem $filesystem;
  45.     }
  46.     /**
  47.      * {@inheritdoc}
  48.      */
  49.     public function resize(ImageInterface $imageResizeConfiguration $configResizeOptions $options): ImageInterface
  50.     {
  51.         if (
  52.             $image->getDimensions()->isUndefined()
  53.             || ($config->isEmpty() && $this->canSkipResize($image$options))
  54.         ) {
  55.             $image $this->createImage($image$image->getPath());
  56.         } else {
  57.             $image $this->processResize($image$config$options);
  58.         }
  59.         if (null !== $options->getTargetPath()) {
  60.             $this->filesystem->copy($image->getPath(), $options->getTargetPath(), true);
  61.             $image $this->createImage($image$options->getTargetPath());
  62.         }
  63.         return $image;
  64.     }
  65.     /**
  66.      * Executes the resize operation via Imagine.
  67.      *
  68.      * @internal Do not call this method in your code; it will be made private in a future version
  69.      */
  70.     protected function executeResize(ImageInterface $imageResizeCoordinates $coordinatesstring $pathResizeOptions $options): ImageInterface
  71.     {
  72.         $dir = \dirname($path);
  73.         if (!$this->filesystem->exists($dir)) {
  74.             $this->filesystem->mkdir($dir);
  75.         }
  76.         $imagineOptions $options->getImagineOptions();
  77.         $imagineImage $image->getImagine()->open($image->getPath());
  78.         if (ImageDimensions::ORIENTATION_NORMAL !== $image->getDimensions()->getOrientation()) {
  79.             (new Autorotate())->apply($imagineImage);
  80.         }
  81.         $imagineImage
  82.             ->resize($coordinates->getSize())
  83.             ->crop($coordinates->getCropStart(), $coordinates->getCropSize())
  84.             ->usePalette(new RGB())
  85.             ->strip()
  86.         ;
  87.         if (isset($imagineOptions['interlace'])) {
  88.             try {
  89.                 $imagineImage->interlace($imagineOptions['interlace']);
  90.             } catch (ImagineRuntimeException $e) {
  91.                 // Ignore failed interlacing
  92.             }
  93.         }
  94.         if (!isset($imagineOptions['format'])) {
  95.             $imagineOptions['format'] = strtolower(pathinfo($pathPATHINFO_EXTENSION));
  96.         }
  97.         // Fix bug with undefined index notice in Imagine
  98.         if ('webp' === $imagineOptions['format'] && !isset($imagineOptions['webp_quality'])) {
  99.             $imagineOptions['webp_quality'] = 80;
  100.         }
  101.         // Atomic write operation
  102.         $tmpPath $this->filesystem->tempnam($dir'img');
  103.         $this->filesystem->chmod($tmpPath0666umask());
  104.         $imagineImage->save($tmpPath$imagineOptions);
  105.         $this->filesystem->rename($tmpPath$pathtrue);
  106.         return $this->createImage($image$path);
  107.     }
  108.     /**
  109.      * Creates a new image instance for the specified path.
  110.      *
  111.      * @internal Do not call this method in your code; it will be made private in a future version
  112.      */
  113.     protected function createImage(ImageInterface $imagestring $path): ImageInterface
  114.     {
  115.         return new Image($path$image->getImagine(), $this->filesystem);
  116.     }
  117.     /**
  118.      * Processes the resize and executes it if not already cached.
  119.      *
  120.      * @internal
  121.      */
  122.     protected function processResize(ImageInterface $imageResizeConfiguration $configResizeOptions $options): ImageInterface
  123.     {
  124.         $coordinates $this->calculator->calculate($config$image->getDimensions(), $image->getImportantPart());
  125.         // Skip resizing if it would have no effect
  126.         if (
  127.             $this->canSkipResize($image$options)
  128.             && !$image->getDimensions()->isRelative()
  129.             && $coordinates->isEqualTo($image->getDimensions()->getSize())
  130.         ) {
  131.             return $this->createImage($image$image->getPath());
  132.         }
  133.         $cachePath $this->cacheDir.'/'.$this->createCachePath($image->getPath(), $coordinates$options);
  134.         if ($this->filesystem->exists($cachePath) && !$options->getBypassCache()) {
  135.             return $this->createImage($image$cachePath);
  136.         }
  137.         return $this->executeResize($image$coordinates$cachePath$options);
  138.     }
  139.     private function canSkipResize(ImageInterface $imageResizeOptions $options): bool
  140.     {
  141.         if (!$options->getSkipIfDimensionsMatch()) {
  142.             return false;
  143.         }
  144.         if (ImageDimensions::ORIENTATION_NORMAL !== $image->getDimensions()->getOrientation()) {
  145.             return false;
  146.         }
  147.         if (
  148.             isset($options->getImagineOptions()['format'])
  149.             && $options->getImagineOptions()['format'] !== strtolower(pathinfo($image->getPath(), PATHINFO_EXTENSION))
  150.         ) {
  151.             return false;
  152.         }
  153.         return true;
  154.     }
  155.     /**
  156.      * Returns the relative target cache path.
  157.      */
  158.     private function createCachePath(string $pathResizeCoordinates $coordinatesResizeOptions $options): string
  159.     {
  160.         $imagineOptions $options->getImagineOptions();
  161.         ksort($imagineOptions);
  162.         $hashData array_merge(
  163.             [
  164.                 Path::makeRelative($path$this->cacheDir),
  165.                 filemtime($path),
  166.                 $coordinates->getHash(),
  167.             ],
  168.             array_keys($imagineOptions),
  169.             array_map(
  170.                 static function ($value) {
  171.                     return \is_array($value) ? implode(','$value) : $value;
  172.                 },
  173.                 array_values($imagineOptions)
  174.             )
  175.         );
  176.         $hash substr(md5(implode('|'$hashData)), 09);
  177.         $pathinfo pathinfo($path);
  178.         $extension $options->getImagineOptions()['format'] ?? strtolower($pathinfo['extension']);
  179.         return $hash[0].'/'.$pathinfo['filename'].'-'.substr($hash1).'.'.$extension;
  180.     }
  181. }