PHP素材圖片拆分

基本邏輯思路
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);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章