vendor/liip/imagine-bundle/Imagine/Filter/FilterManager.php line 88

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of the `liip/LiipImagineBundle` project.
  4.  *
  5.  * (c) https://github.com/liip/LiipImagineBundle/graphs/contributors
  6.  *
  7.  * For the full copyright and license information, please view the LICENSE.md
  8.  * file that was distributed with this source code.
  9.  */
  10. namespace Liip\ImagineBundle\Imagine\Filter;
  11. use Imagine\Image\ImageInterface;
  12. use Imagine\Image\ImagineInterface;
  13. use Liip\ImagineBundle\Binary\BinaryInterface;
  14. use Liip\ImagineBundle\Binary\FileBinaryInterface;
  15. use Liip\ImagineBundle\Binary\MimeTypeGuesserInterface;
  16. use Liip\ImagineBundle\Imagine\Filter\Loader\LoaderInterface;
  17. use Liip\ImagineBundle\Imagine\Filter\PostProcessor\PostProcessorInterface;
  18. use Liip\ImagineBundle\Model\Binary;
  19. class FilterManager
  20. {
  21.     /**
  22.      * @var FilterConfiguration
  23.      */
  24.     protected $filterConfig;
  25.     /**
  26.      * @var ImagineInterface
  27.      */
  28.     protected $imagine;
  29.     /**
  30.      * @var MimeTypeGuesserInterface
  31.      */
  32.     protected $mimeTypeGuesser;
  33.     /**
  34.      * @var LoaderInterface[]
  35.      */
  36.     protected $loaders = [];
  37.     /**
  38.      * @var PostProcessorInterface[]
  39.      */
  40.     protected $postProcessors = [];
  41.     public function __construct(FilterConfiguration $filterConfigImagineInterface $imagineMimeTypeGuesserInterface $mimeTypeGuesser)
  42.     {
  43.         $this->filterConfig $filterConfig;
  44.         $this->imagine $imagine;
  45.         $this->mimeTypeGuesser $mimeTypeGuesser;
  46.     }
  47.     /**
  48.      * Adds a loader to handle the given filter.
  49.      */
  50.     public function addLoader(string $filterLoaderInterface $loader): void
  51.     {
  52.         $this->loaders[$filter] = $loader;
  53.     }
  54.     /**
  55.      * Adds a post-processor to handle binaries.
  56.      */
  57.     public function addPostProcessor(string $namePostProcessorInterface $postProcessor): void
  58.     {
  59.         $this->postProcessors[$name] = $postProcessor;
  60.     }
  61.     public function getFilterConfiguration(): FilterConfiguration
  62.     {
  63.         return $this->filterConfig;
  64.     }
  65.     /**
  66.      * @throws \InvalidArgumentException
  67.      */
  68.     public function apply(BinaryInterface $binary, array $config): BinaryInterface
  69.     {
  70.         $config += [
  71.             'quality' => 100,
  72.             'animated' => false,
  73.         ];
  74.         return $this->applyPostProcessors($this->applyFilters($binary$config), $config);
  75.     }
  76.     public function applyFilters(BinaryInterface $binary, array $config): BinaryInterface
  77.     {
  78.         if ($binary instanceof FileBinaryInterface) {
  79.             $image $this->imagine->open($binary->getPath());
  80.         } else {
  81.             $image $this->imagine->load($binary->getContent());
  82.         }
  83.         foreach ($this->sanitizeFilters($config['filters'] ?? []) as $name => $options) {
  84.             $prior $image;
  85.             $image $this->loaders[$name]->load($image$options);
  86.             if ($prior !== $image) {
  87.                 $this->destroyImage($prior);
  88.             }
  89.         }
  90.         return $this->exportConfiguredImageBinary($binary$image$config);
  91.     }
  92.     /**
  93.      * Apply the provided filter set on the given binary.
  94.      *
  95.      * @param string $filter
  96.      *
  97.      * @throws \InvalidArgumentException
  98.      *
  99.      * @return BinaryInterface
  100.      */
  101.     public function applyFilter(BinaryInterface $binary$filter, array $runtimeConfig = [])
  102.     {
  103.         $config array_replace_recursive(
  104.             $this->getFilterConfiguration()->get($filter),
  105.             $runtimeConfig
  106.         );
  107.         return $this->apply($binary$config);
  108.     }
  109.     /**
  110.      * @throws \InvalidArgumentException
  111.      */
  112.     public function applyPostProcessors(BinaryInterface $binary, array $config): BinaryInterface
  113.     {
  114.         foreach ($this->sanitizePostProcessors($config['post_processors'] ?? []) as $name => $options) {
  115.             $binary $this->postProcessors[$name]->process($binary$options);
  116.         }
  117.         return $binary;
  118.     }
  119.     private function exportConfiguredImageBinary(BinaryInterface $binaryImageInterface $image, array $config): BinaryInterface
  120.     {
  121.         $options = [
  122.             'quality' => $config['quality'],
  123.         ];
  124.         if (isset($config['jpeg_quality'])) {
  125.             $options['jpeg_quality'] = $config['jpeg_quality'];
  126.         }
  127.         if (isset($config['png_compression_level'])) {
  128.             $options['png_compression_level'] = $config['png_compression_level'];
  129.         }
  130.         if (isset($config['png_compression_filter'])) {
  131.             $options['png_compression_filter'] = $config['png_compression_filter'];
  132.         }
  133.         if ('gif' === $binary->getFormat() && $config['animated']) {
  134.             $options['animated'] = $config['animated'];
  135.         }
  136.         $filteredFormat $config['format'] ?? $binary->getFormat();
  137.         try {
  138.             $filteredString $image->get($filteredFormat$options);
  139.         } catch (\Exception $exception) {
  140.             // we don't support converting an animated gif into webp.
  141.             // we can't efficiently check the input data, therefore we retry with target format gif in case of an error.
  142.             if ('webp' !== $filteredFormat || !\array_key_exists('animated'$options) || true !== $options['animated']) {
  143.                 throw $exception;
  144.             }
  145.             $filteredFormat 'gif';
  146.             $filteredString $image->get($filteredFormat$options);
  147.         }
  148.         $this->destroyImage($image);
  149.         return new Binary(
  150.             $filteredString,
  151.             $filteredFormat === $binary->getFormat() ? $binary->getMimeType() : $this->mimeTypeGuesser->guess($filteredString),
  152.             $filteredFormat
  153.         );
  154.     }
  155.     private function sanitizeFilters(array $filters): array
  156.     {
  157.         $sanitized array_filter($filters, function (string $name): bool {
  158.             return isset($this->loaders[$name]);
  159.         }, ARRAY_FILTER_USE_KEY);
  160.         if (\count($filters) !== \count($sanitized)) {
  161.             throw new \InvalidArgumentException(\sprintf('Could not find filter(s): %s'implode(', 'array_map(function (string $name): string { return \sprintf('"%s"'$name); }, array_diff(array_keys($filters), array_keys($sanitized))))));
  162.         }
  163.         return $sanitized;
  164.     }
  165.     private function sanitizePostProcessors(array $processors): array
  166.     {
  167.         $sanitized array_filter($processors, function (string $name): bool {
  168.             return isset($this->postProcessors[$name]);
  169.         }, ARRAY_FILTER_USE_KEY);
  170.         if (\count($processors) !== \count($sanitized)) {
  171.             throw new \InvalidArgumentException(\sprintf('Could not find post processor(s): %s'implode(', 'array_map(function (string $name): string { return \sprintf('"%s"'$name); }, array_diff(array_keys($processors), array_keys($sanitized))))));
  172.         }
  173.         return $sanitized;
  174.     }
  175.     /**
  176.      * We are done with the image object so we can destruct the this because imagick keeps consuming memory if we don't.
  177.      * See https://github.com/liip/LiipImagineBundle/pull/682
  178.      */
  179.     private function destroyImage(ImageInterface $image): void
  180.     {
  181.         if (method_exists($image'__destruct')) {
  182.             $image->__destruct();
  183.         }
  184.     }
  185. }