Image adapters now support saving to GIF/JPG/PNG/WebP (#3869)
authorAlexander Ebert <ebert@woltlab.com>
Mon, 11 Jan 2021 17:33:56 +0000 (18:33 +0100)
committerGitHub <noreply@github.com>
Mon, 11 Jan 2021 17:33:56 +0000 (18:33 +0100)
* Image adapters now support saving to GIF/JPG/PNG/WebP

* Adjusted the usage of exception

* Missing remark on the version support

wcfsetup/install/files/lib/system/image/adapter/GDImageAdapter.class.php
wcfsetup/install/files/lib/system/image/adapter/IImageAdapter.class.php
wcfsetup/install/files/lib/system/image/adapter/ImageAdapter.class.php
wcfsetup/install/files/lib/system/image/adapter/ImagickImageAdapter.class.php

index 9d61e08f67ac7047db35861509ea6eb8331742f5..e68d97e493a804ab4816545728f65cd23fb4c1c1 100644 (file)
@@ -347,8 +347,11 @@ class GDImageAdapter implements IImageAdapter {
                
                ob_start();
                
+               // fix PNG alpha channel handling
+               // see http://php.net/manual/en/function.imagecopymerge.php#92787
                imagealphablending($image, false);
                imagesavealpha($image, true);
+               
                if ($this->type == IMAGETYPE_GIF) {
                        imagegif($image);
                }
@@ -493,6 +496,49 @@ class GDImageAdapter implements IImageAdapter {
                // does nothing
        }
        
+       /**
+        * @inheritDoc
+        */
+       public function saveImageAs($image, string $filename, string $type, int $quality = 100): void {
+               if (!$this->isImage($image)) {
+                       throw new \InvalidArgumentException("Given image is not a valid image resource.");
+               }
+               
+               ob_start();
+               
+               // fix PNG alpha channel handling
+               // see http://php.net/manual/en/function.imagecopymerge.php#92787
+               imagealphablending($image, false);
+               imagesavealpha($image, true);
+               
+               switch ($type) {
+                       case "gif":
+                               imagegif($image);
+                               break;
+                               
+                       case "jpg":
+                       case "jpeg":
+                               imagejpeg($image, null, $quality);
+                               break;
+                               
+                       case "png":
+                               imagepng($image, null, $quality);
+                               break;
+                       
+                       case "webp":
+                               imagewebp($image, null, $quality);
+                               break;
+                       
+                       default:
+                               throw new \InvalidArgumentException("Unreachable");
+               }
+               
+               $stream = ob_get_contents();
+               ob_end_clean();
+               
+               file_put_contents($filename, $stream);
+       }
+       
        /**
         * @inheritDoc
         */
index 4bef9df37375c28922566fd90e7b5caff8d0644c..6b4b9ae4f5c2592d969d0aea0994469f92a2dc34 100644 (file)
@@ -221,6 +221,13 @@ interface IImageAdapter {
         */
        public function overlayImageRelative($file, $position, $margin, $opacity);
        
+       /**
+        * Saves an image using a different file type.
+        * 
+        * @since 5.4
+        */
+       public function saveImageAs($image, string $filename, string $type, int $quality = 100): void;
+       
        /**
         * Determines if an image adapter is supported.
         * 
index 5f68955439e76fe62389ef09b657bf3c6e4dd95c..ff81973d0bf4d21489aac0a92de42340d24b4d07 100644 (file)
@@ -366,6 +366,30 @@ class ImageAdapter implements IImageAdapter, IMemoryAwareImageAdapter {
                return FileUtil::checkMemoryLimit($width * $height * $channels * 2.1);
        }
        
+       /**
+        * @inheritDoc
+        */
+       public function saveImageAs($image, string $filename, string $type, int $quality = 100): void {
+               switch ($type) {
+                       case "gif":
+                       case "jpg":
+                       case "jpeg":
+                       case "png":
+                       case "webp":
+                               break;
+                               
+                       default:
+                               throw new \InvalidArgumentException("Unsupported image format '{$type}'.");
+               }
+               
+               if ($quality < 0 || $quality > 100) {
+                       throw new \InvalidArgumentException("The quality must be an integer between 0 and 100.");
+               }
+               
+               $this->adapter->saveImageAs($image, $filename, $type, $quality);
+       }
+       
+       
        /**
         * @inheritDoc
         */
index eb1912e9ec540fc7bbb9aaa9477d75c4550f8bf0..09ee4073fa732e6e9b4259229c0cec891fbc34e1 100644 (file)
@@ -461,6 +461,46 @@ class ImagickImageAdapter implements IImageAdapter {
                return $match['version'];
        }
        
+       /**
+        * @inheritDoc
+        */
+       public function saveImageAs($image, string $filename, string $type, int $quality = 100): void {
+               if (!($image instanceof \Imagick)) {
+                       throw new \InvalidArgumentException("Given image is not a valid Imagick-object.");
+               }
+               
+               // Greatly reduces the time required to create the image and drastically
+               // reduces the filesize to more reasonable levels without a visible
+               // quality loss.
+               //
+               // See https://github.com/Imagick/imagick/issues/360
+               if ($image->getImageFormat() == "GIF") {
+                       $image = $image->deconstructImages();
+                       $image->quantizeImages(256, \Imagick::COLORSPACE_SRGB, 0, false, false);
+               }
+               
+               switch ($type) {
+                       case "jpg":
+                       case "jpeg":
+                               $fileFormat = "jpg";
+                               break;
+                       
+                       case "png":
+                               $fileFormat = "png";
+                               break;
+                       
+                       case "webp":
+                               $fileFormat = "webp";
+                               break;
+                       
+                       default:
+                               throw new \LogicException("Unreachable");
+               }
+               
+               $image->writeImages("{$fileFormat}:{$filename}", true);
+       }
+       
+       
        /**
         * @param string $version
         * @return bool