From c7a955bc06655d7901e2f71c26389f320fc440a7 Mon Sep 17 00:00:00 2001 From: Stefan Scheu Date: Tue, 19 Oct 2021 09:21:17 +0200 Subject: [PATCH] re-added support for animated gif thumbnails as in https://github.com/pimcore/pimcore/pull/7075 --- .../js/pimcore/settings/thumbnail/item.js | 11 ++- lib/Image/Adapter.php | 23 ++++- lib/Image/Adapter/Imagick.php | 83 +++++++++++++++++-- models/Asset/Image/Thumbnail/Config.php | 21 +++++ models/Asset/Image/Thumbnail/Config/Dao.php | 2 +- models/Asset/Image/Thumbnail/Processor.php | 2 + 6 files changed, 134 insertions(+), 8 deletions(-) diff --git a/bundles/AdminBundle/Resources/public/js/pimcore/settings/thumbnail/item.js b/bundles/AdminBundle/Resources/public/js/pimcore/settings/thumbnail/item.js index 0e4a212086f..7494ab4edd3 100644 --- a/bundles/AdminBundle/Resources/public/js/pimcore/settings/thumbnail/item.js +++ b/bundles/AdminBundle/Resources/public/js/pimcore/settings/thumbnail/item.js @@ -171,6 +171,15 @@ pimcore.settings.thumbnail.item = Class.create({ xtype: "container", html: "(" + t("rasterize_svg_info_text") + ")", style: "margin-bottom: 20px" + }, { + xtype: "checkbox", + name: "preserveAnimation", + boxLabel: t("preserve_animation") + " (Imagick)", + checked: this.data.preserveAnimation + }, { + xtype: "container", + html: "(" + t("preserve_animation_info_text") + ")", + style: "margin-bottom: 20px" }, { xtype: "checkbox", name: "downloadable", @@ -201,7 +210,7 @@ pimcore.settings.thumbnail.item = Class.create({ addMediaPanel: function (name, items, closable, activate) { - if(name.match(/^\d+w$/)) { + if (name.match(/^\d+w$/)) { // convert legacy syntax to new syntax/name name = '(max-width: ' + name.replace("w", "") + 'px)'; } diff --git a/lib/Image/Adapter.php b/lib/Image/Adapter.php index cbc70731f2d..68d7bdb5948 100644 --- a/lib/Image/Adapter.php +++ b/lib/Image/Adapter.php @@ -54,6 +54,11 @@ abstract class Adapter */ protected $preserveColor = false; + /** + * @var bool + */ + protected $preserveAnimation = false; + /** * @var bool */ @@ -530,7 +535,7 @@ protected function reinitializeImage() $this->tmpFiles[] = $tmpFile; $format = 'png32'; - if ($this->isPreserveColor() || $this->isPreserveMetaData()) { + if ($this->isPreserveColor() || $this->isPreserveMetaData() || $this->isPreserveAnimation()) { $format = 'original'; } @@ -637,6 +642,22 @@ public function setPreserveMetaData($preserveMetaData) $this->preserveMetaData = $preserveMetaData; } + /** + * @return bool + */ + public function isPreserveAnimation() + { + return $this->preserveAnimation; + } + + /** + * @param bool $preserveAnimation + */ + public function setPreserveAnimation(bool $preserveAnimation): void + { + $this->preserveAnimation = $preserveAnimation; + } + /** * @return mixed */ diff --git a/lib/Image/Adapter/Imagick.php b/lib/Image/Adapter/Imagick.php index c51c12adb1e..72a5fc1bd72 100644 --- a/lib/Image/Adapter/Imagick.php +++ b/lib/Image/Adapter/Imagick.php @@ -132,6 +132,13 @@ public function load($imagePath, $options = []) $this->setColorspaceToRGB(); } + if ($this->checkPreserveAnimation($i->getImageFormat(), $i, false)) { + if (!$this->resource->readImage($imagePath) || !filesize($imagePath)) { + return false; + } + $this->resource = $this->resource->coalesceImages(); + } + $isClipAutoSupport = \Pimcore::getContainer()->getParameter('pimcore.config')['assets']['image']['thumbnails']['clip_auto_support']; if ($isClipAutoSupport) { // check for the existence of an embedded clipping path (8BIM / Adobe profile meta data) @@ -277,6 +284,11 @@ public function save($path, $format = null, $quality = null) $success = File::put($path, $i->getImageBlob()); } else { $success = $i->writeImage($format . ':' . $path); + if ($this->checkPreserveAnimation($format, $i)) { + $success = $i->writeImages('GIF:' . $path, true); + } else { + $success = $i->writeImage($format . ':' . $path); + } } if (!$success) { @@ -290,6 +302,32 @@ public function save($path, $format = null, $quality = null) return $this; } + /** + * @param string $format + * @param \Imagick|null $i + * @param bool $checkNumberOfImages + * @return bool + */ + protected function checkPreserveAnimation(string $format = "", \Imagick $i = null, bool $checkNumberOfImages = true){ + if (!$this->isPreserveAnimation()) { + return false; + } + + if (!$i) { + $i = $this->resource; + } + + if ($i && $checkNumberOfImages && $i->getNumberImages() <= 1) { + return false; + } + + if ($format && !in_array(strtolower($format), ["gif", "original", "auto"])) { + return false; + } + + return true; + } + /** * @return void */ @@ -494,7 +532,13 @@ public function resize($width, $height) $width = (int)$width; $height = (int)$height; - $this->resource->resizeimage($width, $height, \Imagick::FILTER_UNDEFINED, 1, false); + if ($this->checkPreserveAnimation()) { + foreach ($this->resource as $i => $frame) { + $frame->resizeimage($width, $height, \Imagick::FILTER_UNDEFINED, 1, false); + } + } else { + $this->resource->resizeimage($width, $height, \Imagick::FILTER_UNDEFINED, 1, false); + } $this->setWidth($width); $this->setHeight($height); @@ -543,8 +587,7 @@ public function frame($width, $height, $forceResize = false) $x = ($width - $this->getWidth()) / 2; $y = ($height - $this->getHeight()) / 2; - $newImage = $this->createImage($width, $height); - $newImage->compositeImage($this->resource, \Imagick::COMPOSITE_DEFAULT, $x, $y); + $newImage = $this->createCompositeImageFromResource($width, $height, $x, $y); $this->resource = $newImage; $this->setWidth($width); @@ -586,8 +629,7 @@ public function setBackgroundColor($color) { $this->preModify(); - $newImage = $this->createImage($this->getWidth(), $this->getHeight(), $color); - $newImage->compositeImage($this->resource, \Imagick::COMPOSITE_DEFAULT, 0, 0); + $newImage = $this->createCompositeImageFromResource($this->getWidth(), $this->getHeight(), 0, 0, $color); $this->resource = $newImage; $this->postModify(); @@ -613,6 +655,37 @@ protected function createImage($width, $height, $color = 'transparent') return $newImage; } + /** + * @param int $width + * @param int $height + * @param int $x + * @param int $y + * @param string $color + * @param int $composite + * @return \Imagick + */ + protected function createCompositeImageFromResource($width, $height, $x, $y, $color = 'transparent', $composite = \Imagick::COMPOSITE_DEFAULT) + { + $newImage = null; + if ($this->checkPreserveAnimation()) { + foreach ($this->resource as $i => $frame) { + $imageFrame = $this->createImage($width, $height, $color); + $imageFrame->compositeImage($frame, $composite, $x, $y); + if (!$newImage) { + $newImage = $imageFrame; + } else { + $newImage->addImage($imageFrame); + } + } + } else { + $newImage = $this->createImage($width, $height, $color); + $newImage->compositeImage($this->resource, $composite, $x, $y); + } + return $newImage; + } + + + /** * @param int $angle * diff --git a/models/Asset/Image/Thumbnail/Config.php b/models/Asset/Image/Thumbnail/Config.php index db6b858d8fb..c14d4653464 100644 --- a/models/Asset/Image/Thumbnail/Config.php +++ b/models/Asset/Image/Thumbnail/Config.php @@ -122,6 +122,11 @@ class Config extends Model\AbstractModel */ public $forcePictureTag = false; + /** + * @var bool + */ + public $preserveAnimation = false; + /** * @param string|array|self $config * @@ -880,6 +885,22 @@ public function setForcePictureTag(bool $forcePictureTag): void $this->forcePictureTag = $forcePictureTag; } + /** + * @return bool + */ + public function getPreserveAnimation(): bool + { + return $this->preserveAnimation; + } + + /** + * @param bool $preserveAnimation + */ + public function setPreserveAnimation(bool $preserveAnimation): void + { + $this->preserveAnimation = $preserveAnimation; + } + /** * @return bool */ diff --git a/models/Asset/Image/Thumbnail/Config/Dao.php b/models/Asset/Image/Thumbnail/Config/Dao.php index ddeaa07230a..5e4b043aace 100644 --- a/models/Asset/Image/Thumbnail/Config/Dao.php +++ b/models/Asset/Image/Thumbnail/Config/Dao.php @@ -74,7 +74,7 @@ public function save() $data = []; $allowedProperties = ['name', 'description', 'group', 'items', 'medias', 'format', 'quality', 'highResolution', 'creationDate', 'modificationDate', 'preserveColor', 'preserveMetaData', - 'rasterizeSVG', 'downloadable', 'forcePictureTag', ]; + 'rasterizeSVG', 'downloadable', 'forcePictureTag', 'preserveAnimation']; foreach ($dataRaw as $key => $value) { if (in_array($key, $allowedProperties)) { diff --git a/models/Asset/Image/Thumbnail/Processor.php b/models/Asset/Image/Thumbnail/Processor.php index 700696450ae..66d354cefea 100644 --- a/models/Asset/Image/Thumbnail/Processor.php +++ b/models/Asset/Image/Thumbnail/Processor.php @@ -210,6 +210,8 @@ public static function process(Asset $asset, Config $config, $fileSystemPath = n // transform image $image->setPreserveColor($config->isPreserveColor()); $image->setPreserveMetaData($config->isPreserveMetaData()); + $image->setPreserveAnimation($config->getPreserveAnimation()); + if (!$image->load($fileSystemPath, ['asset' => $asset])) { return self::returnPath($errorImage, $returnAbsolutePath); }