【輕知識】php圖片處理——仿Oss

依賴

php 處理類庫:intervention/image
php擴(kuò)展:imagic

分析需求

截圖自阿里

照貓畫虎。我們要做的事情。就是每步的操作跟Oss保持一致。

  1. / 分隔動作。每步的動作都像是鏈?zhǔn)秸{(diào)用一樣。
  2. 解析每一步的動作。

邏輯實(shí)現(xiàn)

1.解析url中的query。變成規(guī)則。
2.遍歷規(guī)則,調(diào)用不同的處理類。
3.渲染。

代碼實(shí)現(xiàn)

代碼結(jié)構(gòu)

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

旋轉(zhuǎn)圖片示例代碼

class RotateTool
{
    private $processImage;
    private $angle;


    public function __construct(ProcessImageObject $processImage, array $params)
    {
        $this->processImage = $processImage;
        // 解析規(guī)則
        $this->parseParams($params);
    }

    public function parseParams(array $params)
    {
        $this->angle = -array_key_first($params); // 負(fù)號表示順時(shí)針
    }

    public function handle()
    {
        $img = $this->processImage->getProcessor();
        $img->rotate($this->angle);
        return $this->processImage;
    }
}

解析規(guī)則

解析這樣的image/resize,w_200,h_200/rotate,90

    public function parseHandle(string $query)
    {
        $imageRules = explode('/', $query);

        if (!(count($imageRules) > 1)) {
            throw new \Exception('不符合規(guī)則');
        }

        if ('image' === $imageRules[0]) { // 是圖片處理的規(guī)則
            // 開始解析之后的每一條
            $rules = []; // key表示handle名,之后的標(biāo)識處理所需要的參數(shù)。
            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('不符合規(guī)則');
    }

遍歷規(guī)則進(jìn)行渲染

 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']])) {
                // 就開始循環(huán)的去處理
                $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這樣的操作。直接修改該對象的屬性。統(tǒng)一輸出不依賴順序。

ProcessImageObject 的對象代碼。

class ProcessImageObject
{
    public static $limitWidth = 5300;
    public static $limitHeight = 4096;
    /**
     * @var StoragePlatformInterface 桶的所在 客戶端;
     */
    private $client; // 桶的所在 客戶端
    private $content; // 圖片的內(nèi)容
    private $format; // 圖片的格式,
    private $contentType;
    private $quality = 90; // 用于傳遞質(zhì)量,默認(rèn)值為90。因?yàn)閟tream 方法默認(rèn)為90??筛鶕?jù)圖片處理的參數(shù)quality,q_* 來進(jìn)行更改
    private $processor; // 圖像處理者。一次處理實(shí)例化一次 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);
    }

    /**
     * 最后輸出時(shí)使用.
     */
    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,
        ];
    }
}

自定義裁剪

裁剪最主要算坐標(biāo)。

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 是按照右上角為坐標(biāo)

    public function __construct(ProcessImageObject $processImage, array $params)
    {
        $this->processImage = $processImage;
        // 解析規(guī)則
        $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) { // 目標(biāo)寬大于 可用寬
            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,
        ];
    }
}

水印、以及嵌套水印

遍歷規(guī)則,在水印處理類,發(fā)現(xiàn)了有水印圖片。則繼續(xù)解析該水印圖片。相當(dāng)于又走解析規(guī)則處理水印圖。

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 文字旋轉(zhuǎn)角度
     */
    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,默認(rèn)值為 10
        $this->y = $params['y'] ?? 10; // 對照 oss,默認(rèn)值為 10
        $this->g = $params['g'] ?? 'se'; // se 默認(rèn)
        $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 默認(rèn) 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']; // 默認(rèn)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,
        ];
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容