浏览代码

Working with images moved from Core\Image to separate drivers

Visman 3 年之前
父节点
当前提交
21b2b03ff1

+ 27 - 2
app/Core/Files.php

@@ -12,8 +12,10 @@ namespace ForkBB\Core;
 
 use ForkBB\Core\File;
 use ForkBB\Core\Image;
+use ForkBB\Core\Image\DefaultDriver;
 use ForkBB\Core\Exceptions\FileException;
 use InvalidArgumentException;
+use RuntimeException;
 
 class Files
 {
@@ -35,6 +37,12 @@ class Files
      */
     protected $error;
 
+    /**
+     * Класс обработки изображений
+     * @var DefaultDriver
+     */
+    protected $imgDriver;
+
     /**
      * Список mime типов считающихся картинками
      * @var array
@@ -834,7 +842,7 @@ class Files
         'image/avif' => 'avif',
     ];
 
-    public function __construct(/* string|int */ $maxFileSize, /* string|int */ $maxImgSize)
+    public function __construct(/* string|int */ $maxFileSize, /* string|int */ $maxImgSize, array $imgDrivers)
     {
         $init = \min(
             \PHP_INT_MAX,
@@ -849,6 +857,23 @@ class Files
             $this->size($maxFileSize),
             $init
         );
+        $this->imgDriver = $this->imgDriver($imgDrivers);
+    }
+
+    /**
+     * Возращает драйвер для работы с изображениями
+     */
+    protected function imgDriver(array $arr): DefaultDriver
+    {
+        foreach ($arr as $class) {
+            $driver = new $class($this);
+
+            if (true === $driver->ready()) {
+                return $driver;
+            }
+        }
+
+        throw new RuntimeException('No driver for work with images');
     }
 
     /**
@@ -1107,7 +1132,7 @@ class Files
 
         try {
             if (null !== $imageExt) {
-                return new Image($file['tmp_name'], $options);
+                return new Image($file['tmp_name'], $options, $this->imgDriver);
             } else {
                 return new File($file['tmp_name'], $options);
             }

+ 27 - 76
app/Core/Image.php

@@ -12,6 +12,7 @@ namespace ForkBB\Core;
 
 use ForkBB\Core\Files;
 use ForkBB\Core\File;
+use ForkBB\Core\Image\DefaultDriver;
 use ForkBB\Core\Exceptions\FileException;
 use InvalidArgumentException;
 
@@ -19,10 +20,16 @@ class Image extends File
 {
     /**
      * Изображение
-     * @var false|resource
+     * @var mixed
      */
     protected $image;
 
+    /**
+     * Класс обработки изображений
+     * @var DefaultDriver
+     */
+    protected $imgDriver;
+
     /**
      * Качество изображения
      * @var int
@@ -35,18 +42,20 @@ class Image extends File
      */
     protected $pattern = '%^(?!.*?\.\.)([\w.\x5C/:-]*[\x5C/])?(\*|[\w.-]+)\.(\*|[a-z\d]+|\([a-z\d]+(?:\|[a-z\d]+)*\))$%i';
 
-    public function __construct(string $path, array $options)
+    public function __construct(string $path, array $options, DefaultDriver $imgDriver)
     {
         parent::__construct($path, $options);
 
-        if (! \extension_loaded('gd')) {
-            throw new FileException('GD library not enabled');
+        if ($imgDriver::DEFAULT) {
+            throw new FileException('No library for work with images');
         }
 
+        $this->imgDriver = $imgDriver;
+
         if (\is_string($this->data)) {
-            $this->image = \imagecreatefromstring($this->data);
+            $this->image = $imgDriver->readFromStr($this->data);
         } else {
-            $this->image = \imagecreatefromstring(\file_get_contents($this->path));
+            $this->image = $imgDriver->readFromPath($this->path);
         }
 
         if (false === $this->image) {
@@ -59,44 +68,7 @@ class Image extends File
      */
     public function resize(int $maxW, int $maxH): Image
     {
-        $oldW   = \imagesx($this->image);
-        $oldH   = \imagesy($this->image);
-        $wr     = ($maxW < 1) ? 1 : $maxW / $oldW;
-        $hr     = ($maxH < 1) ? 1 : $maxH / $oldH;
-        $r      = \min($wr, $hr, 1);
-        $width  = (int) \round($oldW * $r);
-        $height = (int) \round($oldH * $r);
-
-        if (false === ($image = \imagecreatetruecolor($width, $height))) {
-            throw new FileException('Failed to create new truecolor image');
-        }
-        if (false === ($color = \imagecolorallocatealpha($image, 255, 255, 255, 127))) {
-            throw new FileException('Failed to create color for image');
-        }
-        if (false === \imagefill($image, 0, 0, $color)) {
-            throw new FileException('Failed to fill image with color');
-        }
-
-        \imagecolortransparent($image, $color);
-        $palette = \imagecolorstotal($this->image);
-
-        if (
-            $palette > 0
-            && ! \imagetruecolortopalette($image, true, $palette)
-        ) {
-            throw new FileException('Failed to convert image to palette');
-        }
-        if (
-            false === \imagealphablending($image, false)
-            || false === \imagesavealpha($image, true)
-        ) {
-            throw new FileException('Failed to adjust image');
-        }
-        if (false === \imagecopyresampled($image, $this->image, 0, 0, 0, 0, $width, $height, $oldW, $oldH)) {
-            throw new FileException('Failed to resize image');
-        }
-
-        $this->image = $image;
+        $this->image = $this->imgDriver->resize($this->image, $maxW, $maxH);
 
         return $this;
     }
@@ -128,43 +100,22 @@ class Image extends File
      */
     protected function fileProc(string $path): bool
     {
-        switch (\pathinfo($path, \PATHINFO_EXTENSION)) {
-            case 'jpg':
-                $result = \imagejpeg($this->image, $path, $this->quality);
-                break;
-            case 'png':
-                $quality = (int) \floor((100 - $this->quality) / 11);
-                $result  = \imagepng($this->image, $path, $quality);
-                break;
-            case 'gif':
-                $result = \imagegif($this->image, $path);
-                break;
-            case 'webp':
-                $result = \imagewebp($this->image, $path, $this->quality);
-                break;
-            case 'avif':
-                $result = \imageavif($this->image, $path, $this->quality);
-                break;
-            default:
-                $this->error = 'File type not supported';
-
-                return false;
-        }
+        $result = $this->imgDriver->writeToPath($this->image, $path, $this->quality);
 
-        if (! $result) {
+        if (null === $result) {
+            $result      = false;
+            $this->error = 'File type not supported';
+        } elseif (! $result) {
             $this->error = 'Error writing file';
-
-            return false;
+        } else {
+            \chmod($path, 0644);
         }
 
-        \chmod($path, 0644);
-
-        return true;
+        return $result;
     }
 
-    public function __destruct() {
-        if (\is_resource($this->image)) {
-            \imagedestroy($this->image);
-        }
+    public function __destruct()
+    {
+        $this->imgDriver->destroy($this->image);
     }
 }

+ 64 - 0
app/Core/Image/DefaultDriver.php

@@ -0,0 +1,64 @@
+<?php
+/**
+ * This file is part of the ForkBB <https://github.com/forkbb>.
+ *
+ * @copyright (c) Visman <mio.visman@yandex.ru, https://github.com/MioVisman>
+ * @license   The MIT License (MIT)
+ */
+
+declare(strict_types=1);
+
+namespace ForkBB\Core\Image;
+
+use ForkBB\Core\Files;
+use ForkBB\Core\Exceptions\FileException;
+
+class DefaultDriver
+{
+    const DEFAULT = true;
+
+    /**
+     * @var bool
+     */
+    protected $ready;
+
+    /**
+     * @var Files
+     */
+    protected $files;
+
+    public function __construct(Files $files)
+    {
+        $this->ready = true;
+        $this->files = $files;
+    }
+
+    public function ready(): bool
+    {
+        return $this->ready;
+    }
+
+    public function readFromStr(string $data) /* : mixed|false */
+    {
+        return false;
+    }
+
+    public function readFromPath(string $path) /* : mixed|false */
+    {
+        return false;
+    }
+
+    public function writeToPath(/* mixed */ $image, string $path, int $quality): ?bool
+    {
+        return null;
+    }
+
+    public function resize(/* mixed */ $image, int $maxW, int $maxH) /* : mixed */
+    {
+        return $image;
+    }
+
+    public function destroy(/* mixed */ $image): void
+    {
+    }
+}

+ 127 - 0
app/Core/Image/GDDriver.php

@@ -0,0 +1,127 @@
+<?php
+/**
+ * This file is part of the ForkBB <https://github.com/forkbb>.
+ *
+ * @copyright (c) Visman <mio.visman@yandex.ru, https://github.com/MioVisman>
+ * @license   The MIT License (MIT)
+ */
+
+declare(strict_types=1);
+
+namespace ForkBB\Core\Image;
+
+use ForkBB\Core\Files;
+use ForkBB\Core\Image\DefaultDriver;
+use ForkBB\Core\Exceptions\FileException;
+
+class GDDriver extends DefaultDriver
+{
+    const DEFAULT = false;
+
+    public function __construct(Files $files)
+    {
+        parent::__construct($files);
+
+        $this->ready = \extension_loaded('gd') && \function_exists('\\imagecreatetruecolor');
+    }
+
+    public function readFromStr(string $data) /* : mixed|false */
+    {
+        return $this->ready ? \imagecreatefromstring($data) : false;
+    }
+
+    public function readFromPath(string $path) /* : mixed|false */
+    {
+        if (
+            ! $this->ready
+            || $this->files->isBadPath($path)
+        ) {
+            return false;
+        } else {
+            return \imagecreatefromstring(\file_get_contents($path));
+        }
+    }
+
+    public function writeToPath(/* mixed */ $image, string $path, int $quality): ?bool
+    {
+        $args = [$image, $path];
+        $type = \pathinfo($path, \PATHINFO_EXTENSION);
+
+        switch ($type) {
+            case 'gif':
+                break;
+            case 'png':
+                $args[] = (int) \floor((100 - $quality) / 11);
+                break;
+            case 'jpg':
+                $type = 'jpeg';
+            case 'webp':
+            case 'avif':
+                $args[] = $quality;
+                break;
+            default:
+                return null;
+        }
+
+        $function = '\\image' . $type;
+
+        if (\function_exists($function)) {
+            return $function(...$args);
+        } else {
+            return null;
+        }
+    }
+
+    public function resize(/* mixed */ $image, int $maxW, int $maxH) /* : mixed */
+    {
+        if (! $this->ready) {
+            throw new FileException('GD library not enabled');
+        }
+
+        $oldW   = \imagesx($image);
+        $oldH   = \imagesy($image);
+        $wr     = ($maxW < 1) ? 1 : $maxW / $oldW;
+        $hr     = ($maxH < 1) ? 1 : $maxH / $oldH;
+        $r      = \min($wr, $hr, 1);
+        $width  = (int) \round($oldW * $r);
+        $height = (int) \round($oldH * $r);
+
+        if (false === ($result = \imagecreatetruecolor($width, $height))) {
+            throw new FileException('Failed to create new truecolor image');
+        }
+        if (false === ($color = \imagecolorallocatealpha($result, 255, 255, 255, 127))) {
+            throw new FileException('Failed to create color for image');
+        }
+        if (false === \imagefill($result, 0, 0, $color)) {
+            throw new FileException('Failed to fill image with color');
+        }
+
+        \imagecolortransparent($result, $color);
+        $palette = \imagecolorstotal($image);
+
+        if (
+            $palette > 0
+            && ! \imagetruecolortopalette($result, true, $palette)
+        ) {
+            throw new FileException('Failed to convert image to palette');
+        }
+        if (
+            false === \imagealphablending($result, false)
+            || false === \imagesavealpha($result, true)
+        ) {
+            throw new FileException('Failed to adjust image');
+        }
+        if (false === \imagecopyresampled($result, $image, 0, 0, 0, 0, $width, $height, $oldW, $oldH)) {
+            throw new FileException('Failed to resize image');
+        }
+
+        return $result;
+    }
+
+    public function destroy(/* mixed */ $image): void
+    {
+        if (\is_resource($image)) {
+            \imagedestroy($image);
+        }
+    }
+}

+ 29 - 0
app/Core/Image/ImagickDriver.php

@@ -0,0 +1,29 @@
+<?php
+/**
+ * This file is part of the ForkBB <https://github.com/forkbb>.
+ *
+ * @copyright (c) Visman <mio.visman@yandex.ru, https://github.com/MioVisman>
+ * @license   The MIT License (MIT)
+ */
+
+declare(strict_types=1);
+
+namespace ForkBB\Core\Image;
+
+use ForkBB\Core\Files;
+use ForkBB\Core\Image\DefaultDriver;
+use ForkBB\Core\Exceptions\FileException;
+
+class ImagickDriver extends DefaultDriver
+{
+    const DEFAULT = false;
+
+    public function __construct(Files $files)
+    {
+        parent::__construct($files);
+
+        $this->ready = \extension_loaded('imagick') && \class_exists('\\Imagick');
+    }
+
+
+}

+ 21 - 0
app/Models/Pages/Admin/Update.php

@@ -1593,4 +1593,25 @@ class Update extends Admin
 
         return null;
     }
+
+    /**
+     * rev.38 to rev.39
+     */
+    protected function stageNumber38(array $args): ?int
+    {
+        $coreConfig = new CoreConfig($this->configFile);
+
+        $coreConfig->add(
+            'shared=>Files=>drivers',
+            [
+                '\\ForkBB\\Core\\Image\\ImagickDriver::class',
+                '\\ForkBB\\Core\\Image\\GDDriver::class',
+                '\\ForkBB\\Core\\Image\\DefaultDriver::class',
+            ]
+        );
+
+        $coreConfig->save();
+
+        return null;
+    }
 }

+ 1 - 1
app/bootstrap.php

@@ -52,7 +52,7 @@ if (
 }
 $c->PUBLIC_URL = $c->BASE_URL . $forkPublicPrefix;
 
-$c->FORK_REVISION = 38;
+$c->FORK_REVISION = 39;
 $c->START         = $forkStart;
 $c->DIR_APP       = __DIR__;
 $c->DIR_PUBLIC    = \realpath(__DIR__ . '/../public');

+ 8 - 3
app/config/main.dist.php

@@ -173,9 +173,14 @@ return [
             'flag'  => \ENT_HTML5,
         ],
         'Files' => [
-            'class' => \ForkBB\Core\Files::class,
-            'file'  => '%MAX_FILE_SIZE%',
-            'img'   => '%MAX_IMG_SIZE%',
+            'class'   => \ForkBB\Core\Files::class,
+            'file'    => '%MAX_FILE_SIZE%',
+            'img'     => '%MAX_IMG_SIZE%',
+            'drivers' => [
+                \ForkBB\Core\Image\ImagickDriver::class,
+                \ForkBB\Core\Image\GDDriver::class,
+                \ForkBB\Core\Image\DefaultDriver::class,
+            ],
         ],
 
         'VLnoURL'    => \ForkBB\Models\Validators\NoURL::class,