基本逻辑思路
1、纵向依次从左往向找非背景色的位置定义为左边的界线点
2、从左边界线点往右找有背景色的位置预定为右边界线点
3、从左边的界线点到右边的界线点之间向上依次找所有像素都为背景色预定为上边界线
4、从左边的界线点到右边的界线点之间向下依次找所有像素都为背景色预定为下边界线
5、从上边界线到下边界线以右边界线点之间排查是否有非背景色,有则向右移一位再跳到步骤2
6、返回提取的左右上下坐标及大小位置
7、抠出图片
环境要求
1、php7.2+
2、GD库扩展
逻辑代码
/**
* 抠图分离处理
*/
class ImageSplit {
/**
* @var resource 图像资源
*/
protected $image;
/**
* @var int 图像x轴长度
*/
protected $width;
/**
* @var int 图像y轴长度
*/
protected $height;
/**
* @var array 抠出占用区块集
*/
protected $occupies = [];
/**
* @var array 分界色值
*/
protected $divideColor = ['red' => 0, 'green' => 0, 'blue' => 0, 'alpha' => 127];
/**
* 初始化处理
* @param string $filename
* @param array $divideColor
*/
public function __construct(string $filename, array $divideColor = null) {
$image = imagecreatefrompng($filename);
imagesavealpha($image, true);
$this->width = imagesx($image) - 1;
$this->height = imagesy($image) - 1;
$this->image = $image;
if ($divideColor) {
$this->divideColor = array_merge($this->divideColor, $divideColor);
}
if ($this->divideColor['alpha'] == 127) {
$this->divideColor = ['alpha' => 127];
}
}
/**
* 保存抠图
* @param string $savepath
* @param int $quality
* @param int $size
* @return array
*/
public function save(string $savepath, int $quality = 3, int $size = 4) {
if (!file_exists($savepath)) {
mkdir($savepath, 0777, true);
}
$path = realpath($savepath) . DIRECTORY_SEPARATOR;
$array = [];
foreach ($this->each() as $key => $item) {
$image = $item[0];
// 过滤掉太小的图片
if (imagesx($image) < $size && imagesy($image) < $size) {
continue;
}
$filename = "{$path}X({$item[1]})-Y({$item[2]})_{$key}.png";
imagesavealpha($image, true);
if (imagepng($image, $filename, $quality)) {
$array[] = $filename;
}
}
return $array;
}
/**
* 循环提取抠图资源
* @return yield
*/
public function each() {
for ($startW = 0; $startW <= $this->width; $startW += 1) {
// 提取这列里占用的行集
$array = [];
foreach ($this->occupies as $item) {
if ($startW >= $item[0] && $startW <= $item[2]) {
$array[] = $item;
}
}
for ($startH = 0; $startH <= $this->height; $startH += 1) {
//判断是否有占用行,有就跳过
foreach ($array as $item) {
if ($startH >= $item[1] && $startH <= $item[3]) {
$startH = $item[3];
continue 2;
}
}
//判断是否有内容
if ($this->boundary($startW, $startH, true)) {
yield $this->digout($startW, $startH);
$array[] = end($this->occupies);
}
}
}
}
/**
* 找出合适区块并抠出图片资源
* @param int $startW
* @param int $startH
* @return array
*/
protected function digout(int $startW, int $startH) {
$rightW = $startW + 1;
$bottomH = $topH = $startH;
do { // 循环找出可抠区域
$rightH = $startH;
$rightW = $this->getRight($rightW, $rightH);
$topH = $this->getTop($startW, $rightW, $topH);
$bottomH = $this->getBottom($startW, $rightW, $bottomH);
$startH = $this->getFracture($rightW, $topH, $bottomH);
} while ($startH !== null);
$this->occupies[] = [$startW, $topH, $rightW, $bottomH];
return [imagecrop($this->image, ['x' => $startW, 'y' => $topH, 'width' => $rightW - $startW + ($rightW >= $this->width ? 1 : 0), 'height' => $bottomH - $topH + ($bottomH >= $this->height ? 1 : 0)]), $startW, $topH];
}
/**
* 判断是否为抠图边界
* @param int $startW
* @param int $startH
* @param bool $direction
* @return bool
*/
protected function boundary(int $startW, int $startH, bool $direction) {
$equal = true;
if ($this->width > $startW && $this->height > $startH) {
$color = imagecolorsforindex($this->image, imagecolorat($this->image, $startW, $startH));
foreach ($this->divideColor as $name => $value) {
if ($color[$name] != $value) {
$equal = false;
break;
}
}
}
return $equal ^ $direction;
}
/**
* 获取抠图区块断开位置
* @param int $startW
* @param int $startH
* @param int $endH
* @return int
*/
protected function getFracture(int $startW, int $startH, int $endH) {
if ($startW >= $this->width) {
return;
}
for (; $startH <= $endH; $startH += 1) {
if ($this->boundary($startW, $startH, true)) {
return $startH;
}
}
return;
}
/**
* 获取右边抠图最近合适位置
* @param int $startW
* @param int $startH
* @return int
*/
protected function getRight(int $startW, int $startH) {
for (; $startW < $this->width; $startW += 1) {
if ($this->boundary($startW, $startH, false)) {
break;
}
}
return $startW;
}
/**
* 获取上边抠图最近合适的位置
* @param int $startW
* @param int $endW
* @param int $startH
* @return int
*/
protected function getTop(int $startW, int $endW, int $startH) {
for (; $startH > 0; $startH -= 1) {
for ($x = $startW; $x <= $endW; $x += 1) {
if ($this->boundary($x, $startH, true)) {
continue 2;
}
}
break;
}
return $startH;
}
/**
* 获取下边抠图最近合适的位置
* @param int $startW
* @param int $endW
* @param int $startH
* @return int
*/
protected function getBottom(int $startW, int $endW, int $startH) {
for (; $startH < $this->height; $startH += 1) {
for ($x = $startW; $x <= $endW; $x += 1) {
if ($this->boundary($x, $startH, true)) {
continue 2;
}
}
break;
}
return $startH;
}
}
测试验证代码
$dir = '拆分后保存目录'
$file = '要拆分的素材图片'
$imgSplit = new ImageSplit($file);
$files = $imgSplit->save($dir);