PHP圖片集合並處理

基本邏輯思路
1、計算出圖片集的總寬和高,提取出最大寬和高
2、當最大寬和高度超出總寬度或高度的1/4則只按橫向或縱向直接依次合併,跳到步驟13
3、取出所有圖片有大小與位置,並進行排序(寬度+高度的各從大到小排)
4、初始空間爲總寬度的1/4,開始橫向加圖
5、判斷圖片集是否還有圖片,如果沒有則跳到步驟13
6、依次取出一張可以放入空餘空間的圖片並計算出剩下空餘空間,並記錄這個圖片的位置和增加到下排空餘空間集,直到沒有合適的圖片可以放入
7、有貼圖則把最後有剩餘空間的數據增加到下排剩餘空間集,沒有合併新圖跳到步驟9
8、下排剩餘空間集按高度從小到大排序
9、依次取出最小高度的下排剩餘空間,跳到步驟5
10、依次取出最小高度的下排剩餘空間與上次沒有合併新圖的剩餘空間嘗試進行合併,如果相鄰則合併成功並跳到步驟5
11、把不能合併的剩餘空間添加到臨時集合中,當臨時集合的數據個數超過下排空餘空間集個數時則強制合併循環這些剩餘空間合併相鄰一個再放入下排空餘空間集中
12、跳到步驟5
13、取出合併後最大的寬和高












環境要求
1、php7.2+
2、GD擴展

合併邏輯處理代碼

/**
 * 合併碎圖
 */
class ImageMerge {

    /**
     * @var array 支持處理圖片
     */
    const IMAGE_CREATE_FUNC = [
        IMAGETYPE_GIF => 'imagecreatefromgif',
        IMAGETYPE_JPEG => 'imagecreatefromjpeg',
        IMAGETYPE_PNG => 'imagecreatefrompng',
        IMAGETYPE_BMP => 'imagecreatefrombmp',
        IMAGETYPE_WBMP => 'imagecreatefromwbmp',
        IMAGETYPE_XBM => 'imagecreatefromxbm',
    ];

    /**
     * @var int 寬度索引
     */
    const KEY_WIDTH = 0;

    /**
     * @var int 高度索引
     */
    const KEY_HEIGHT = 1;

    /**
     * @var int 圖片類型索引
     */
    const KEY_TYPE = 2;

    /**
     * @var int 圖片文件名索引
     */
    const KEY_FILENAME = 3;

    /**
     * @var int 圖片X軸座標索引
     */
    const KEY_X = 4;

    /**
     * @var int 圖片Y軸座標索引
     */
    const KEY_Y = 5;

    /**
     * @var array 要處理的圖像集
     */
    protected $images = [];

    /**
     * @var array 背景色值
     */
    protected $bgColor = ['red' => 0, 'green' => 0, 'blue' => 0, 'alpha' => 127];

    /**
     * 初始化處理
     * @param array $bgColor
     */
    public function __construct(array $bgColor = null) {
        if ($bgColor) {
            $this->bgColor = array_merge($this->bgColor, $bgColor);
        }
    }

    /**
     * 添加合併圖片所在目錄
     * @param string $dir
     * @param bool $recursion
     */
    public function addDir(string $dir, bool $recursion = true) {
        if (file_exists($dir) && is_dir($dir) && $handle = opendir($dir)) {
            while (false !== $file = readdir($handle)) {
                if ($file == '.' || $file == '..') {
                    continue;
                }
                $path = $dir . DIRECTORY_SEPARATOR . $file;
                if (is_dir($file)) {
                    $recursion && $this->addDir($path, $recursion);
                } else {
                    $this->add($path);
                }
            }
        }
    }

    /**
     * 添加要合併的圖片集
     * @param string $files
     */
    public function addFile(string ...$files) {
        foreach ($files as $file) {
            $this->add($file);
        }
    }

    /**
     * 保存合併圖片
     * @param string $savepath
     * @param int $space
     * @param int $quality
     * @return bool
     */
    public function save(string $savepath = null, int $space = 2, int $quality = 3) {
        if ($image = $this->make($space)) {
            if ($savepath) {
                $dir = dirname($savepath);
                if (!file_exists($dir)) {
                    mkdir($dir, 0777, true);
                }
            }
            return imagepng($image, $savepath, $quality);
        }
        return false;
    }

    /**
     * 生成圖片
     * @param int $space
     * @return resource|null
     */
    public function make(int $space = 2) {
        if (count($this->images)) {
            list($array, $width, $height) = $this->equidistribution($space);
            $image = $this->create($width, $height);
            foreach ($array as $img) {
                imagecopy($image, call_user_func(static::IMAGE_CREATE_FUNC[$img[static::KEY_TYPE]], $img[static::KEY_FILENAME]), $img[static::KEY_X], $img[static::KEY_Y], 0, 0, $img[static::KEY_WIDTH], $img[static::KEY_HEIGHT]);
            }
            return $image;
        }
    }

    /**
     * 分佈處理
     * @param int $space
     * @return array
     */
    protected function equidistribution(int $space) {
        $maxwidth = $totalwidth = $maxheight = $totalheight = 0;
        foreach ($this->images as $item) {
            $totalwidth += $item[static::KEY_WIDTH];
            $totalheight += $item[static::KEY_HEIGHT];
            $maxwidth = max($maxwidth, $item[static::KEY_WIDTH]);
            $maxheight = max($maxheight, $item[static::KEY_HEIGHT]);
        }
        $theory = ceil($totalwidth / 4);
        if ($theory < $maxwidth) { //直接橫放
            list($array, $width) = $this->oneway($space, static::KEY_Y, static::KEY_X, static::KEY_WIDTH, static::KEY_HEIGHT);
            $height = $maxheight;
        } elseif ($totalheight / 4 < $maxheight) { //直接堅放
            list($array, $height) = $this->oneway($space, static::KEY_X, static::KEY_Y, static::KEY_HEIGHT, static::KEY_WIDTH);
            $width = $maxwidth;
        } else { //鋪開
            return $this->spread($this->images, $space, $theory + $space);
        }
        return [$array, $width, $height];
    }

    /**
     * 均勻鋪開處理
     * @param array $images
     * @param int $space
     * @param int $maxWidth
     * @return array
     */
    protected function spread(array $images, int $space, int $maxWidth) {
        $lists = $heights = $array = [];
        $surplusWidth = $maxWidth;
        $x = $y = 0;
        usort($images, function($prev, $next) {
            return $prev[static::KEY_WIDTH] + $prev[static::KEY_HEIGHT] < $next[static::KEY_WIDTH] + $next[static::KEY_HEIGHT] ? 1 : -1;
        });
        while ($count = count($images)) {
            $surplusWidth -= $space;
            foreach ($images as $key => $img) {
                if ($img[static::KEY_WIDTH] <= $surplusWidth) {
                    $width = $img[static::KEY_WIDTH] + $space;
                    $img[static::KEY_X] = $x;
                    $img[static::KEY_Y] = $y;
                    $array[] = $img;
                    $heights[] = [static::KEY_WIDTH => $width, static::KEY_X => $x, static::KEY_Y => $y + $img[static::KEY_HEIGHT] + $space];
                    $surplusWidth -= $width;
                    $x += $width;
                    unset($images[$key]);
                    if ($surplusWidth <= 0) {
                        break;
                    }
                }
            }
            $surplusWidth += $space;
            if ($count > count($images)) {//上面找到合適的需要新位置
                if ($surplusWidth > 0) {//追加多餘部分
                    $heights[] = [static::KEY_WIDTH => $surplusWidth, static::KEY_X => $x, static::KEY_Y => $y];
                }
                $heights = $this->sortHeights(array_merge($lists, $heights));
                $lists = [];
            } else {// 上面沒合適的需要位置
                $prev = [static::KEY_WIDTH => $surplusWidth, static::KEY_X => $x, static::KEY_Y => $y];
                if (count($heights)) {
                    $merge = $this->mergeAdjoin([$prev, $heights[0]]);
                    if (count($merge) == 1) { //合併確認是否爲相鄰
                        $heights[0] = end($merge);
                        goto INIT_SET;
                    }
                }
                $lists[] = $prev;
                if (count($heights) <= count($lists)) {
                    $heights = $this->mergeAdjoin(array_merge($lists, $heights));
                    $lists = [];
                }
            }
            INIT_SET: [static::KEY_X => $x, static::KEY_Y => $y, static::KEY_WIDTH => $surplusWidth] = array_shift($heights);
        }
        [$width, $height] = $this->getMaxSize($array);
        return [$array, $width, $height];
    }

    /**
     * 獲取最大空間
     * @param array $array
     * @return array
     */
    protected function getMaxSize(array $array) {
        $maxWidth = $maxHeight = 0;
        foreach ($array as $item) {
            $width = $item[static::KEY_WIDTH] + $item[static::KEY_X];
            $height = $item[static::KEY_Y] + $item[static::KEY_HEIGHT];
            if ($width > $maxWidth) {
                $maxWidth = $width;
            }
            if ($height > $maxHeight) {
                $maxHeight = $height;
            }
        }
        return [$maxWidth, $maxHeight];
    }

    /**
     * 位置高度排序
     * @param array $heights
     * @return array
     */
    protected function sortHeights(array $heights) {
        usort($heights, function($prev, $next) {
            return $prev[static::KEY_Y] > $next[static::KEY_Y] ? 1 : -1;
        });
        return $heights;
    }

    /**
     * 合併相鄰的位置
     * @param array $heights
     * @return array
     */
    protected function mergeAdjoin(array $heights) {
        $array = [];
        while (count($heights)) {
            $current = array_shift($heights);
            [static::KEY_X => $x, static::KEY_Y => $y, static::KEY_WIDTH => $surplusWidth] = $current;
            foreach ($heights as $key => $item) {
                if ($item[static::KEY_X] + $item[static::KEY_WIDTH] == $x) {//相鄰右邊
                    $x = $item[static::KEY_X];
                } elseif ($item[static::KEY_X] - $surplusWidth != $x) {//不相鄰右邊
                    continue;
                }
                $array[] = [static::KEY_X => $x, static::KEY_Y => $item[static::KEY_Y], static::KEY_WIDTH => $surplusWidth + $item[static::KEY_WIDTH]];
                unset($heights[$key]);
                continue 2;
            }
            $array[] = $current;
        }
        return $this->sortHeights($array);
    }

    /**
     * 單向放
     * @param int $space
     * @param int $fixed
     * @param int $move
     * @param int $size
     * @return array
     */
    protected function oneway(int $space, int $fixed, int $move, int $size) {
        $array = [];
        $pos = 0;
        foreach ($this->images as $img) {
            $img[$move] = $pos;
            $img[$fixed] = 0;
            $pos += $space + $img[$size];
            $array[] = $img;
        }
        return [$array, $pos - $space];
    }

    /**
     * 創建底圖
     * @param int $width
     * @param int $height
     * @return resource
     */
    protected function create(int $width, int $height) {
        $image = imagecreatetruecolor($width, $height);
        imagesavealpha($image, true);
        $color = imagecolorallocatealpha($image, $this->bgColor['red'], $this->bgColor['green'], $this->bgColor['blue'], $this->bgColor['alpha']);
        imagefill($image, 0, 0, $color);
        return $image;
    }

    /**
     * 添加圖片文件
     * @param string $filename
     */
    protected function add(string $filename) {
        if (file_exists($filename) && strpos(mime_content_type($filename), 'image/') === 0 && (false !== $array = getimagesize($filename)) && isset(static::IMAGE_CREATE_FUNC[$array[2]])) {
            $this->images[] = [
                static::KEY_WIDTH => $array[0],
                static::KEY_HEIGHT => $array[1],
                static::KEY_TYPE => $array[2],
                static::KEY_FILENAME => $filename,
            ];
        }
    }

}

測試驗證代碼

    //合併
        $dir = '要合併的圖片集目錄'
    $imgMerge = new ImageMerge();
    $imgMerge->addDir($dir);
    $imgMerge->save($dir .  '/merge-image.png', 5);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章