依赖
php 处理类库:intervention/image
php扩展:imagic
分析需求
照猫画虎。我们要做的事情。就是每步的操作跟Oss保持一致。
- / 分隔动作。每步的动作都像是链式调用一样。
- 解析每一步的动作。
逻辑实现
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,
];
}
}
自定义裁剪
裁剪最主要算坐标。
代码如下
<?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,
];
}
}