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

依赖

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

代码如下

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)