vendor/contao/image/src/PictureGenerator.php line 85

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 Contao\ImagineSvg\Imagine as ImagineSvg;
  12. class PictureGenerator implements PictureGeneratorInterface
  13. {
  14.     /**
  15.      * @var ResizerInterface
  16.      */
  17.     private $resizer;
  18.     /**
  19.      * @var ResizeCalculator
  20.      */
  21.     private $calculator;
  22.     /**
  23.      * @var ResizeOptions
  24.      */
  25.     private $resizeOptions;
  26.     public function __construct(ResizerInterface $resizerResizeCalculator $calculator null)
  27.     {
  28.         if (null === $calculator) {
  29.             $calculator = new ResizeCalculator();
  30.         }
  31.         $this->resizer $resizer;
  32.         $this->calculator $calculator;
  33.     }
  34.     /**
  35.      * {@inheritdoc}
  36.      */
  37.     public function generate(ImageInterface $imagePictureConfiguration $configResizeOptions $options): PictureInterface
  38.     {
  39.         $this->resizeOptions = clone $options;
  40.         $this->resizeOptions->setTargetPath(null);
  41.         $formats $this->getFormatsFromConfig(
  42.             $config,
  43.             strtolower(pathinfo($image->getPath(), PATHINFO_EXTENSION))
  44.         );
  45.         $sources = [];
  46.         foreach ($config->getSizeItems() as $sizeItem) {
  47.             foreach ($formats as $index => $format) {
  48.                 $sources[] = $this->generateSource($image$sizeItem$format$index === \count($formats));
  49.             }
  50.         }
  51.         $source $this->generateSource($image$config->getSize(), array_pop($formats), true);
  52.         foreach ($formats as $format) {
  53.             $sources[] = $this->generateSource($image$config->getSize(), $formatfalse);
  54.         }
  55.         return new Picture($source$sources);
  56.     }
  57.     /**
  58.      * Generates the source.
  59.      */
  60.     private function generateSource(ImageInterface $imagePictureConfigurationItem $configstring $formatbool $lastFormat): array
  61.     {
  62.         $densities = [1];
  63.         $sizesAttribute $config->getSizes();
  64.         $width1x $this->calculator
  65.             ->calculate(
  66.                 $config->getResizeConfig(),
  67.                 new ImageDimensions($image->getDimensions()->getSize(), true),
  68.                 $image->getImportantPart()
  69.             )
  70.             ->getCropSize()
  71.             ->getWidth()
  72.         ;
  73.         if (
  74.             $config->getDensities()
  75.             && ($config->getResizeConfig()->getWidth() || $config->getResizeConfig()->getHeight())
  76.         ) {
  77.             if (!$sizesAttribute && false !== strpos($config->getDensities(), 'w')) {
  78.                 $sizesAttribute '100vw';
  79.             }
  80.             if (!$image->getImagine() instanceof ImagineSvg) {
  81.                 $densities $this->parseDensities($config->getDensities(), $width1x);
  82.             }
  83.         }
  84.         $attributes = [];
  85.         $srcset = [];
  86.         $descriptorType $sizesAttribute 'w' 'x'// use pixel density descriptors if the sizes attribute is empty
  87.         foreach ($densities as $density) {
  88.             $srcset[] = $this->generateSrcsetItem($image$config$density$descriptorType$width1x$format);
  89.         }
  90.         $srcset $this->removeDuplicateScrsetItems($srcset);
  91.         $attributes['srcset'] = $srcset;
  92.         $attributes['src'] = $srcset[0][0];
  93.         if (!$attributes['src']->getDimensions()->isRelative() && !$attributes['src']->getDimensions()->isUndefined()) {
  94.             $attributes['width'] = $attributes['src']->getDimensions()->getSize()->getWidth();
  95.             $attributes['height'] = $attributes['src']->getDimensions()->getSize()->getHeight();
  96.         }
  97.         if ($sizesAttribute) {
  98.             $attributes['sizes'] = $sizesAttribute;
  99.         }
  100.         if ($config->getMedia()) {
  101.             $attributes['media'] = $config->getMedia();
  102.         }
  103.         if (!$lastFormat) {
  104.             $attributes['type'] = $this->getMimeFromFormat($format);
  105.         }
  106.         return $attributes;
  107.     }
  108.     /**
  109.      * Parse the densities string and return an array of scaling factors.
  110.      *
  111.      * @return array<int,float>
  112.      */
  113.     private function parseDensities(string $densitiesint $width1x): array
  114.     {
  115.         $densitiesArray explode(','$densities);
  116.         $densitiesArray array_map(
  117.             static function ($density) use ($width1x) {
  118.                 $type substr(trim($density), -1);
  119.                 if ('w' === $type) {
  120.                     return (int) $density $width1x;
  121.                 }
  122.                 return (float) $density;
  123.             },
  124.             $densitiesArray
  125.         );
  126.         // Strip empty densities
  127.         $densitiesArray array_filter($densitiesArray);
  128.         // Add 1x to the beginning of the list
  129.         array_unshift($densitiesArray1);
  130.         // Strip duplicates
  131.         return array_values(array_unique($densitiesArray));
  132.     }
  133.     /**
  134.      * Generates a srcset item.
  135.      *
  136.      * @param string $descriptorType x, w or the empty string
  137.      *
  138.      * @return array Array containing an ImageInterface and an optional descriptor string
  139.      */
  140.     private function generateSrcsetItem(ImageInterface $imagePictureConfigurationItem $configfloat $densitystring $descriptorTypeint $width1xstring $format): array
  141.     {
  142.         $resizeConfig = clone $config->getResizeConfig();
  143.         $resizeConfig->setWidth((int) round($resizeConfig->getWidth() * $density));
  144.         $resizeConfig->setHeight((int) round($resizeConfig->getHeight() * $density));
  145.         $options = clone $this->resizeOptions;
  146.         $imagineOptions $options->getImagineOptions();
  147.         $imagineOptions['format'] = $format;
  148.         $options->setImagineOptions($imagineOptions);
  149.         $resizedImage $this->resizer->resize($image$resizeConfig$options);
  150.         $src = [$resizedImage];
  151.         if ('x' === $descriptorType) {
  152.             $srcX $resizedImage->getDimensions()->getSize()->getWidth() / $width1x;
  153.             $src[1] = rtrim(rtrim(sprintf('%.3F'$srcX), '0'), '.').'x';
  154.         } elseif ('w' === $descriptorType) {
  155.             $src[1] = $resizedImage->getDimensions()->getSize()->getWidth().'w';
  156.         }
  157.         return $src;
  158.     }
  159.     /**
  160.      * Removes duplicate items from a srcset array.
  161.      *
  162.      * @param array $srcset Array containing an ImageInterface and an optional descriptor string
  163.      */
  164.     private function removeDuplicateScrsetItems(array $srcset): array
  165.     {
  166.         $srcset array_values(array_filter(
  167.             $srcset,
  168.             static function (array $item) use (&$usedPaths) {
  169.                 /** @var array<ImageInterface> $item */
  170.                 $key $item[0]->getPath();
  171.                 if (isset($usedPaths[$key])) {
  172.                     return false;
  173.                 }
  174.                 $usedPaths[$key] = true;
  175.                 return true;
  176.             }
  177.         ));
  178.         if (=== \count($srcset) && isset($srcset[0][1]) && 'x' === substr($srcset[0][1], -1)) {
  179.             unset($srcset[0][1]);
  180.         }
  181.         return $srcset;
  182.     }
  183.     /**
  184.      * @return array<string>
  185.      */
  186.     private function getFormatsFromConfig(PictureConfiguration $configstring $sourceFormat): array
  187.     {
  188.         $formatsConfig $config->getFormats();
  189.         return array_map(
  190.             static function ($format) use ($config$sourceFormat) {
  191.                 return $format === $config::FORMAT_DEFAULT $sourceFormat $format;
  192.             },
  193.             $formatsConfig[$sourceFormat] ?? $formatsConfig[$config::FORMAT_DEFAULT]
  194.         );
  195.     }
  196.     private function getMimeFromFormat(string $format): string
  197.     {
  198.         static $mapping = [
  199.             'jpg' => 'image/jpeg',
  200.             'wbmp' => 'image/vnd.wap.wbmp',
  201.             'svg' => 'image/svg+xml',
  202.         ];
  203.         return $mapping[$format] ?? 'image/'.$format;
  204.     }
  205. }