依賴
php 處理類庫:intervention/image
php擴(kuò)展:imagic
分析需求

截圖自阿里
照貓畫虎。我們要做的事情。就是每步的操作跟Oss保持一致。
- / 分隔動作。每步的動作都像是鏈?zhǔn)秸{(diào)用一樣。
- 解析每一步的動作。
邏輯實(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,
];
}
}