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);
}