GD库实现自动裁剪PNG图片多余透明区域

发布时间:2023年12月25日

? ? ? ? AI抠图可以自动将图片中主体扣取出来,但是图片尺寸不变,就导致主体周边存在多余的透明图层:

现在需要将多余透明图层自动删除掉。

之前只知道imagecrop(),按理说顺序应该是检测确定图片主体的x,y坐标及裁剪宽度和高度。

首先确定起始坐标值和位置,可以通过一张四个角有不同颜色的图片来确定:

测试代码:

//获取图片指定位置rgba值
$im = ImageCreateFromPng("./img/02.png");
$imgIndex = ImageColorAt($im, 0, 0); //坐标从0开始
$colorInfo = imagecolorsforindex($im, $imgIndex);
$imgIndex2 = ImageColorAt($im, 699, 437);
$colorInfo2 = imagecolorsforindex($im, $imgIndex2);
var_dump($colorInfo, $colorInfo2);

通过简单调整上面代码的参数和打印出来的结果就可以确定,坐标值是从0开始的,且坐标方向是从左上角到右下角。完全透明时colorInfo数组中的‘alpha’值为127。

因为AI抠图返回的数据是base64格式(也可以返回路径,这里需求是要返回内容),所以要写的自动裁剪方法接收两个参数,一个是图片base64数据,一个是保存图片的路径,方法如下

//获取最小非透明区域图片
function getMinAreaImg($imgStr,$saveFile)
{
    if(empty($imgStr)){
        return false;
    }
    $data = base64_decode($imgStr);
    $im = imagecreatefromstring($data);

    if($im !== false){
        $size = getimagesizefromstring($data);
        $width = $size[0];
        $height = $size[1];
        $area = [
            'left' => [0, 0],
            'right' => ['width' => $width, 'height' => $height]
        ];
        $lw = -1;//左裁剪点w坐标
        $lh = -1;//左裁剪点h坐标
        $firstPoint = [0, 0]; //第一个发现点,没用
        //查找左裁剪点
        for ($w=0; $w < $width; $w++) { 
            for ($h=0; $h < $height; $h++) { 
                $imgIndex = ImageColorAt($im, $w, $h);
                $colorInfo = imagecolorsforindex($im, $imgIndex);
                if($colorInfo['alpha'] != 127){
                    $firstPoint = [$w, $h];
                    $lw = $w;
                    $lh = $h;
                    if($lh == 0){
                        break 2;
                    }
                    //已确定左w,下面确定左h(右上角区域最小h)
                    for ($ow=$lw+1; $ow < $width; $ow++) { 
                        for ($oh=$lh-1; $oh >= 0; $oh--) { 
                            $ooindex = ImageColorAt($im, $ow, $oh);
                            $ooinfo = imagecolorsforindex($im, $ooindex);
                            if($ooinfo['alpha'] != 127){
                                $lh = $oh;
                                if($lh == 0){
                                    break 4;
                                }
                            }
                        }
                    }
                    break 2;
                }
            }
        }

        if($lw == -1 && $lh == -1){//纯透明图层
            return false;
        }

        $rw = $width-1;//右裁剪点w坐标
        $rh = $height-1;//右裁剪点h坐标
        //右下角到左裁剪点查找右裁剪点
        for ($sw=$width-1; $sw >= $lw; $sw--) { //这里条件使用>=$lw或>=$firstPoint[0]都行(第一个发现点是采用宽度优先得来的)
            for ($sh=$height-1; $sh >= $lh; $sh--) { //这里条件需要使用>=$lh,使用高度最小值(而不是第一个发现点,发现点是宽度优先得来的)才能在下面的查找中找到最大的宽度值
                $sgIndex = ImageColorAt($im, $sw, $sh);
                $sgColorInfo = imagecolorsforindex($im, $sgIndex);
                if($sgColorInfo['alpha'] != 127){
                    $rw = $sw;
                    $rh = $sh;
                    if($rh == $height-1){
                        break 2;
                    }
                    for ($sow=$rw-1; $sow >= $lw; $sow--) { 
                        for ($soh=$rh+1; $soh <= $height-1; $soh++) { 
                            $sogIndex = ImageColorAt($im, $sow, $soh);
                            $sogColorInfo = imagecolorsforindex($im, $sogIndex);
                            if($sogColorInfo['alpha'] != 127){
                                $rh = $soh;
                                if($rh == $height-1){
                                    break 4;
                                }
                            }
                        }
                    }
                    break 2;
                }
            }
        }
        //根据左右裁剪点,裁剪图片
        $cropWidth = $rw - $lw + 1;
        $cropHeight = $rh - $lh + 1;
        // var_dump($lw,$lh,$cropWidth,$cropHeight);exit;
        $im2 = imagecrop($im, ['x' => $lw, 'y' => $lh, 'width' => $cropWidth, 'height' => $cropHeight]);
        if ($im2 !== FALSE) {
            imagesavealpha($im2, true); //保存图像时是否保留完整的 alpha 通道信息
            imagepng($im2, $saveFile);
            imagedestroy($im2);
        }
        imagedestroy($im);
        return true;
    }

    return false;
}

整体思路是从左上角开始,以w优先,遍历h,先确定最左边(最小)w,然后再遍历右上角区域确定最小h,示例图(椭圆是主体)如下:

这样就确定了左上角起始坐标了,然后确定右下角坐标,从图片右下角开始,相同的道理,如下紫色区域(结束位置即上面确定的起始坐标位置)是计算需要考虑的范围。

这样又得出了最小裁剪目标的右下角坐标。

剩下的就是计算裁剪宽高和保存图片了,具体看上述代码。

测试一下:

$img = "./img/01.png";
$saveFile = './img/01_crop.png';
$imgStr = base64_encode(file_get_contents($img));
$result = getMinAreaImg($imgStr,$saveFile);
var_dump($result);

结果符合预期。

以为到这里结束了?其实不然。

在我保存裁剪图片后,发现图片透明图片丢失了,于是各种调试并查找官方文档,解决方案就是上述代码中imagesavealpha方法。
但令我崩溃是的在翻阅的过程中发现了imagecropauto()方法。auto?这明摆着就是可以自动裁剪吗,

PHP: imagecropauto - Manual

点进去一看,还真是。imagecropauto两个参数,第一个参数基本明确,主要是第二个参数存在几种类型【IMG_CROP_DEFAULT,IMG_CROP_TRANSPARENT,IMG_CROP_BLACK,IMG_CROP_WHITE,IMG_CROP_SIDES,IMG_CROP_THRESHOLD】
于是又写了一个小方法,

//自动裁剪
function cropAutoImg($imgStr,$saveFile)
{
    if(empty($imgStr)){
        return false;
    }
    $data = base64_decode($imgStr);
    $im = imagecreatefromstring($data);

    if($im !== false){
        $cropped = imagecropauto($im, IMG_CROP_SIDES);
        if ($cropped !== false) { 
            imagesavealpha($cropped, true); //保存图像时是否保留完整的 alpha 通道信息
            imagepng($cropped, $saveFile);
            imagedestroy($cropped);
        }
        imagedestroy($im);
        return true;
    }
    return false;
}

大概测试了一下,乍一看似乎是IMG_CROP_TRANSPARENT,但事与愿违,根据我提供的图片的裁剪基本确定IMG_CROP_SIDES才是符合我预期的,IMG_CROP_WHITE适合白色背景的图片,IMG_CROP_BLACK应该是适合黑色背景的吧(没测,感兴趣的可以测一下),IMG_CROP_THRESHOLD似乎跟颜色阈值有关没有深究。至于IMG_CROP_TRANSPARENT可能是我理解错了,不想费时间去探索了,有知道怎么使用的大佬希望提供下示例。

有时候对文档的熟练掌握真的能够事半功倍,但这次尝试自己编写自动裁剪也是个不错的体验吧,也希望这篇文章对大家有用。

文章来源:https://blog.csdn.net/jinborui2/article/details/135202251
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。