PHP image collection and processing

Basic logic
1. Calculate the total width and height of the picture set, and extract the maximum width and height.
2. When the maximum width and height exceed 1/4 of the total width or height, they will only be merged horizontally or vertically in turn, skip to step 13
3. Take out all the pictures with size and position, and sort them (the width + height are from large to small)
4. The initial space is 1/4 of the total width, start to add
pictures horizontally 5. Determine whether the picture set still exists Picture, if there is no picture, skip to step 13
6. Take out a picture that can be put into the free space and calculate the remaining free space, and record the position of this picture and add it to the next set of free space until there is no suitable Picture can be put in
7. If there is a map, the data with the last remaining space will be added to the remaining space set in the lower row. If the new image is not merged, skip to step 9
8. The remaining space set in the lower row will be sorted by height from small to large.
9. Take out the smallest in order The remaining space of the lower row of height, skip to step 5
10. Take out the remaining space of the lower row of minimum height and the remaining space of the last unmerged new image in order to try to merge, if it is adjacent, the merge is successful and skip to step 5
11. The remaining space that cannot be combined is added to the temporary collection. When the number of data in the temporary collection exceeds the number of the lower empty space set, the merge cycle is forced to merge these remaining spaces together and then put into the lower empty space set
12, Skip to step 5
13. Take out the largest width and height after merging

Environmental requirements
1, php7.2+
2, GD extension

Merging logic processing code

/**
 * 合并碎图
 */
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,
            ];
        }
    }

}

Test verification code

    //合并
        $dir = '要合并的图片集目录'
    $imgMerge = new ImageMerge();
    $imgMerge->addDir($dir);
    $imgMerge->save($dir .  '/merge-image.png', 5);

Guess you like

Origin blog.51cto.com/php2012web/2606218