美文网首页PHP经验分享PHP开发
【轻知识】php图片处理——仿Oss

【轻知识】php图片处理——仿Oss

作者: 言十年 | 来源:发表于2020-07-20 23:06 被阅读0次

    依赖

    php 处理类库:intervention/image
    php扩展:imagic

    分析需求

    截图自阿里

    照猫画虎。我们要做的事情。就是每步的操作跟Oss保持一致。

    1. / 分隔动作。每步的动作都像是链式调用一样。
    2. 解析每一步的动作。

    逻辑实现

    1.解析url中的query。变成规则。
    2.遍历规则,调用不同的处理类。
    3.渲染。

    代码实现

    代码结构

    ImageHandle
    ├── CircleTool.php
    ├── CropTool.php
    ├── FormatTool.php
    ├── ImageHandleInterface.php
    ├── InfoTool.php
    ├── QualityTool.php
    ├── ResizeTool.php
    ├── RotateTool.php
    └── WatermarkTool.php
    

    接口类

    interface ImageHandleInterface
    {
        public function parseParams(array $params);
        public function handle();
    }
    

    旋转图片示例代码

    class RotateTool
    {
        private $processImage;
        private $angle;
    
    
        public function __construct(ProcessImageObject $processImage, array $params)
        {
            $this->processImage = $processImage;
            // 解析规则
            $this->parseParams($params);
        }
    
        public function parseParams(array $params)
        {
            $this->angle = -array_key_first($params); // 负号表示顺时针
        }
    
        public function handle()
        {
            $img = $this->processImage->getProcessor();
            $img->rotate($this->angle);
            return $this->processImage;
        }
    }
    

    解析规则

    解析这样的image/resize,w_200,h_200/rotate,90

        public function parseHandle(string $query)
        {
            $imageRules = explode('/', $query);
    
            if (!(count($imageRules) > 1)) {
                throw new \Exception('不符合规则');
            }
    
            if ('image' === $imageRules[0]) { // 是图片处理的规则
                // 开始解析之后的每一条
                $rules = []; // key表示handle名,之后的标识处理所需要的参数。
                foreach ($imageRules as $rk => $rule) {
                    $ruleArr = explode(',', $rule);
                    foreach ($ruleArr as $pk => $param) {
                        if (0 === $pk) {
                            $rules[$rk]['handle'] = $ruleArr[0];
                            $rules[$rk]['params'] = [];
                        } else {
                            $parmArr = explode('_', $param, 2);
                            $rules[$rk]['params'][$parmArr[0]] = $parmArr[1] ?? null;
                        }
                    }
                }
    
                return $rules;
            }
            throw new \Exception('不符合规则');
        }
    

    遍历规则进行渲染

     public function getImage1(StoragePlatformInterface $client, string $key, string $process)
        {
            $meta = $client->headObject($key);
            $imgSize = $meta['content-length'];
    
            $imageContent = $client->getFileContent($key);
            if (empty($process)) { // 直接输出图片
                return $imageContent;
            }
    
            $processImageObject = new ProcessImageObject($client, $imageContent);
            // 走图片处理
            $handleRules = $this->parseHandle($process);
            foreach ($handleRules as $handle) {
                if (isset($this->imageHandles[$handle['handle']])) {
                    // 就开始循环的去处理
                    $handleTool = new $this->imageHandles[$handle['handle']]($processImageObject, $handle['params']);
                    $processImageObject = $handleTool->handle();
                }
            }
            $response = Context::get(ResponseInterface::class);
            $response = $response->withHeader('Content-type', $processImageObject->getContentType());
            Context::set(ResponseInterface::class, $response);
    
            return $processImageObject->stream();
        }
    

    使用ProcessImageObject对象管理处理的图片。方便管理。另外,像format跟quality这样的操作。直接修改该对象的属性。统一输出不依赖顺序。

    ProcessImageObject 的对象代码。

    class ProcessImageObject
    {
        public static $limitWidth = 5300;
        public static $limitHeight = 4096;
        /**
         * @var StoragePlatformInterface 桶的所在 客户端;
         */
        private $client; // 桶的所在 客户端
        private $content; // 图片的内容
        private $format; // 图片的格式,
        private $contentType;
        private $quality = 90; // 用于传递质量,默认值为90。因为stream 方法默认为90。可根据图片处理的参数quality,q_* 来进行更改
        private $processor; // 图像处理者。一次处理实例化一次 Imanager 然后传递下去。
    
        /**
         * @var int 图像的高
         */
        private $height;
    
        /**
         * @var int 图像的宽
         */
        private $width;
    
        public function __construct(StoragePlatformInterface $client, $content)
        {
            $this->client = $client;
            $this->content = $content;
            $manager = new ImageManager(['driver' => 'gd']);
            $img = $manager->make($this->content);
            $mime = $img->mime();
    
            $this->format = explode('/', $mime)[1];
            $this->contentType = $mime;
            $this->processor = $img;
            $this->height = $img->getHeight();
            $this->width = $img->getWidth();
            if ($this->width > self::$limitWidth || $this->height > self::$limitHeight) {
                throw new StorageException(ErrorResultCode::IMAGE_WIDTH_HEIGHT_LARGE, 403);
            }
        }
    
        public function getClient(): StoragePlatformInterface
        {
            return $this->client;
        }
    
        public function getProcessor()
        {
            return $this->processor;
        }
    
        public function stream(): StreamInterface
        {
            return $this->processor->stream($this->format, $this->quality);
        }
    
        /**
         * 最后输出时使用.
         */
        public function getContentType(): string
        {
            return $this->contentType;
        }
    
        public function setContentType(string $contentType)  {
            $this->contentType = $contentType;
        }
        public function setQuality(int $q = 90): void
        {
            $this->quality = $q;
        }
    
        public function setFormat(string $format): void
        {
            $this->format = $format;
            $this->contentType = 'image/'.$this->format;
        }
    
        public function getWidth()
        {
            return $this->processor->getWidth();
        }
    
        public function getHeight()
        {
            return $this->processor->getHeight();
        }
    
        public function setContent(string $content)
        {
            $this->content = $content;
        }
    
        public function toArray(): array
        {
            return [
                'client' => [
                    'bucket' => $this->client->getBucketName(),
                ],
                'format' => $this->format,
                'quality ' => $this->quality,
                'width'=>$this->width,
                'height'=>$this->height,
            ];
        }
    }
    

    自定义裁剪

    裁剪最主要算坐标。

    image.png

    代码如下

    <?php
    
    declare(strict_types=1);
    
    namespace App\Lib\ImageHandle;
    
    use App\Object\ProcessImageObject;
    
    class CropTool implements ImageHandleInterface
    {
        private $image;
    
        //image/crop,x_10,y_10,w_200,h_200,g_se
        private $x;
        private $y;
        private $w;
        private $h;
        private $g; //nw、north、ne、west、center、east、sw、south、se
    
        /**
         * @var ProcessImageObject
         */
        private $processImage;
    
        // ne 是按照右上角为坐标
    
        public function __construct(ProcessImageObject $processImage, array $params)
        {
            $this->processImage = $processImage;
            // 解析规则
            $this->parseParams($params);
        }
    
        public function parseParams(array $params)
        {
            $this->x = $params['x'];
            $this->y = $params['y'];
            $this->w = $params['w'];
            $this->h = $params['h'];
            $this->g = $params['g'];
        }
    
        public function handle()
        {
            $img = $this->processImage->getProcessor();
            // 如果g 存在 ,则按照 g 去做偏移
            $originH = $img->getHeight();
            $originW = $img->getWidth();
            $halfX = (int) ($originW / 2);
            $halfY = (int) ($originH / 2);
            $centerX = (int) ($originW / 2);
            $centerY = (int) ($originH / 2);
            $oneThirdX = floor($originW / 3);
            $oneThirdY = floor($originH / 3);
            $rightTopX = $originW;
            $rightTopY = 0;
            $leftBottomY = $originH;
            $leftBottomX = $originW;
            if (empty($this->h)) {
                $this->h = $originH;
            }
            if (empty($this->w)) {
                $this->w = $originW;
            }
            if ('nw' === $this->g) {
                $x = 0 + $this->x;
                $y = 0 + $this->y;
                // 如果 超出了边界则 截止到边界
                $limitX = $x + $this->w;
            } elseif ('north' == $this->g) {
                $x = $this->x + ($halfX - floor($this->w / 2));
                $y = $this->y;
                // 如果 超出了边界则 截止到边界
                $limitX = $x + $this->w;
            } elseif ('ne' == $this->g) {
                $x = $rightTopX - $this->w + $this->x;
                $y = $this->y;
                // 如果 超出了边界则 截止到边界
                $limitX = $x + $this->w;
            } elseif ('west' == $this->g) {
                $x = 0 + $this->x;
                $y = ($halfY - floor($this->h / 2)) + $this->y;
                // 如果 超出了边界则 截止到边界
                $limitX = $x + $this->w;
            } elseif ('center' === $this->g) {
                $x = $this->x + ($halfX - floor($this->w / 2));
                $y = ($halfY - floor($this->h / 2)) + $this->y;
                // 如果 超出了边界则 截止到边界
                $limitX = $x + $this->w;
            } elseif ('east' === $this->g) {
                $x = $rightTopX - $this->w + $this->x;
                $y = ($halfY - floor($this->h / 2)) + $this->y;
                // 如果 超出了边界则 截止到边界
                $limitX = $x + $this->w;
            } elseif ('sw' === $this->g) {
                $x = 0 + $this->x;
                $y = $leftBottomY - $this->h + $this->y;
                // 如果 超出了边界则 截止到边界
                $limitX = $x + $this->w;
            } elseif ('south' === $this->g) {
                $x = $this->x + ($halfX - floor($this->w / 2));
                $y = $leftBottomY - $this->h + $this->y;
                // 如果 超出了边界则 截止到边界
                $limitX = $x + $this->w;
            } elseif ('se' == $this->g) {
                $x = $rightTopX - $this->w + $this->x;
                $y = $leftBottomY - $this->h + $this->y;
                // 如果 超出了边界则 截止到边界
                $limitX = $x + $this->w;
            }
            if ($limitX > $originW) { // 目标宽大于 可用宽
                if (($originW - $x) < 0) {
                    $x = $originW - $this->w;
                    $w = $this->w;
                } else {
                    $w = $originW - $x;
                }
            } else {
                $w = $this->w;
            }
    
            $limitY = $y + $this->h;
            if ($limitY > $originH) {
                if (($originH - $y) < 0) {
                    $y = $originH - $this->h;
                    $h = $this->h;
                } else {
                    $h = ($originH - $y);
                }
            } else {
                $h = $this->h;
            }
            $this->x = $x;
            $this->y = $y;
            $this->w = $w;
            $this->h = $h;
            $img->crop($this->w, $this->h, $this->x, $this->y);
    
            return $this->processImage;
        }
    
        public function toArray()
        {
            return [
                'w' => $this->w,
                'h' => $this->h,
                'x' => $this->x,
                'y' => $this->y,
                'g' => $this->g,
            ];
        }
    }
    
    

    水印、以及嵌套水印

    遍历规则,在水印处理类,发现了有水印图片。则继续解析该水印图片。相当于又走解析规则处理水印图。

    class WatermarkTool implements ImageHandleInterface
    {
        public $image;
        /**
         * @var int 透明度
         */
        public $t;
        public $g;
        public $x;
        public $y;
        public $voffset;
        public $markImage;
    
        public $fontDir = BASE_PATH.'/src/font/';
        /**
         * @var string 字体
         */
        public $text;
        /**
         * @var string 字体颜色
         */
        public $color;
        /**
         * @var int 字体大小
         */
        public $size;
    
        /**
         * @var int 文字旋转角度
         */
        public $rotate;
    
        /**
         * @var ProcessImageObject
         */
        private $processImage;
    
    
        public $waterMarkPosMap = [
            'nw' => 'top-left',
            'north' => 'top',
            'ne' => 'top-right',
    
            'west' => 'left',
            'center' => 'center',
            'east' => 'right',
    
            'sw' => 'bottom-left',
            'south' => 'bottom',
            'se' => 'bottom-right', // default
        ];
    
    
        public function __construct(ProcessImageObject $processImage, array $params)
        {
            $this->processImage = $processImage;
    
            $this->parseParams($params);
        }
    
        public function parseParams(array $params)
        {
            // TODO: Implement parseParams() method.
            $this->x = $params['x'] ?? 10; // 对照 oss,默认值为 10
            $this->y = $params['y'] ?? 10; // 对照 oss,默认值为 10
            $this->g = $params['g'] ?? 'se'; // se 默认
            $this->t = $params['t'] ?? 90;
            $this->markImage = !empty($params['image']) ? urldecode(Base64Tool::urlsafeDecode($params['image'])) : '';
            $this->text = !empty($params['text']) ? Base64Tool::urlsafeDecode($params['text']) : '';
    
            $this->color = '#'.($params['color'] ?? '000000');
            $this->size = $params['size'] ?? 40; // 对照oss 默认 40
            $this->rotate = -($params['rotate'] ?? 0);
        }
    
        public function handle()
        {
            $img = $this->processImage->getProcessor();
    
            if (!empty($this->markImage)) {
                $imageService = new ImageService();
                $parseArr = parse_url($this->markImage);
                $process = '';
                if (isset($parseArr['query'])) {
                    $query = explode('=', $parseArr['query']);
                    $process = $query[1];
                }
                $pos = $this->waterMarkPosMap['se']; // 默认se 也就是 bottom-right
                if (isset($this->waterMarkPosMap[$this->g])) {
                    $pos = $this->waterMarkPosMap[$this->g];
                }
                $object = $imageService->getImage2($this->processImage->getClient(), $parseArr['path'], $process);
                $object->getProcessor()->opacity($this->t);
                $img->insert($object->getProcessor()->stream(), $pos, $this->x, $this->y);
            } elseif (!empty($this->text)) { // text_b3Nz5paH5a2X5rC05Y2wMQ,size_20,x_10,y_200
                $img->text($this->text, $this->x, $this->y, function ($font) {
                    $font->file($this->fontDir.'wqy-zenhei.ttf');
                    $font->size($this->size);
                    $font->color($this->color);
                    $font->angle($this->rotate);
                });
            }
    
            return $this->processImage;
        }
    
        public function toArray()
        {
            return [
                'x' => $this->x,
                'y' => $this->y,
                'markImage' => $this->markImage,
            ];
        }
    }
    

    相关文章

      网友评论

        本文标题:【轻知识】php图片处理——仿Oss

        本文链接:https://www.haomeiwen.com/subject/knuukktx.html