本来是想用ImageMagick来实现这个功能,但是ImageMagick 的环境搭建了半天没搞定,就换成了GD库。等有时间再去研究ImageMagick吧。
为了简化业务人员的工作流程,需要在系统上开发一个小功能,用于拼接图片。原先的工作,需要业务人员通过ps去拼接一个长图。需求是省去ps的环节,业务人员只需上传图片,并制定每个图片的title。就能自动生成一张长图。
以下开发步骤,为个人开发过程处理思路。
分析需求目标
通过分析得知。一份长图可以大致分为四个部分。
一个大标题图。
一个logo图
一个图片小标题图。
一张内容图。
当然,除了大标题外,其他的图肯定不止一个。只是相同点是这些。剩下的是如何去拼接这个循环体。
需求拆分
清楚需求后,就可以开始动手去实现。但是一个整体功能,无法下手。这时候,需要将大功能拆分成一个个小功能去实现。
等比例压缩图片
因为上传的图片并非同一大小,而拼接的长图需要一个统一的宽度。所以需要将不同尺寸的图片转换成相同的宽度。
public function PicCompress($src,$out_with=468){
// 获取图片基本信息
list($width, $height, $type, $attr) = getimagesize($src);
// 获取图片后缀名
$pictype = image_type_to_extension($type,false);
// 拼接方法
$imagecreatefrom = "imagecreatefrom".$pictype;
// 打开传入的图片
$in_pic = $imagecreatefrom($src);
// 压缩后的图片长宽
$new_width = $out_with;
$new_height = $out_with/$width*$height;
// 生成中间图片
$temp = imagecreatetruecolor($new_width,$new_height);
// 图片按比例合并在一起。
imagecopyresampled($temp,$in_pic,0,0,0,0,$new_width,$new_height,$width,$height);
// 销毁输入图片
imagedestroy($in_pic);
return array($temp,$new_width,$new_height);
}
思路是这样,首先获取传入图片的信息。获取图片的宽高。等比例转换宽高后,使用压缩后的宽高创建一张空白图片。再将两张图片合并。结果就是一张等比例缩放的图片。
计算拼图的高
因为gd库拼图的原理是先生成一张长图,再把每一个图片拼到长图上。因此我们需要先知道这张长图的高是多少(宽为确定值)。
public function PicALLHeight($arr,$width){
$height = 0;
if(count($arr) == count($arr,1)){ //一位数组的计算
foreach ($arr as $key => $value) {
$info = getimagesize($value);
$height += $width/$info[0]*$info[1];
}
}else{
foreach ($arr as $key => $value) { //二维数组的计算
foreach ($value as $k => $v) {
$info = getimagesize($v);
$height += $width/$info[0]*$info[1];
}
}
}
return $height;
}
这里我直接根据我的业务,写了一个。函数中的判断为一维数组还是二维数组。思路是先获取传入图的宽高,通过确定的宽度,求缩放后的高度。再将每个高求和返回。即为内容图的高度。
将文字转换成图片
这一步比较复杂,我使用了一个简单的算法去解决。
对于文字部分的需求是这样的。要求文字必须居中处理。因为gd库并不支持html标签生成。所以需要计算来确定文字是否居中。
这是我第一版本解决方法
public function Fonttopic(){
// header("Content-type: image/jpeg");
// 给文字指定一个编码,避免乱码
mb_internal_encoding("UTF-8");
$im =imagecreate(468,100);
$background_color = ImageColorAllocate ($im, 255, 255, 255);
$col = imagecolorallocate($im, 0, 0, 0);
$font="fonts/WRYH.ttf"; //字体所放目录
$come="凤的天啊!-年舍得离开-阿斯蒂了空间发拉克丝敦伦尽分拉看似简单法兰克圣诞节放按时交电费的李开复加速离开的减肥拉数据东风逻辑了";
$txt_max_width = intval(0.8*468);
$content = "";
for ($i=0;$i<mb_strlen($come);$i++) {
$letter[] = mb_substr($come, $i, 1);
}
foreach ($letter as $l) {
$teststr = $content." ".$l;
$testbox = imagettfbbox(10,0,$font,$teststr);
// 判断拼接后的字符串是否超过预设的宽度。超出宽度添加换行
if (($testbox[2] > $txt_max_width) && ($content !== "")) {
$content .= "\n";
}
$content .= $l;
}
$txt_width = $testbox[2]-$testbox[0];
$y = 100 * 0.5; // 文字从何处的高度开始
$x = (468 - $txt_width) / 2; //文字居中
// echo $x;die;
imagettftext($im,10,0,$x,$y,$col,$font,$content); //写 TTF 文字到图中
// die;
// imagejpeg($im);
return $im;
}
思路。将文字字符串,拆分成单个字。存到数组中。再通过遍历数组。不断增加字,去判断这行字是否超出所设定的宽度。如果超出则换行。
执行结果
执行之后发现,忽略了一个问题。这种做法最后一行没法居中。
第二个解决方法
为了解决这个问题,我把加了换行符的字符串。又拆分成一个数组。再去遍历这个数组,一行一行的网图片上拼。
public function FontToPic($text,$font_size=10,$pic_hight=100,$pic_width=468){
// header("Content-type: image/jpeg");
mb_internal_encoding("UTF-8");
$im =imagecreate($pic_width,$pic_hight);
$background_color = ImageColorAllocate ($im, 255, 255, 255);
$col = imagecolorallocate($im, 0, 0, 0);
$font="fonts/WRYH.ttf"; //字体所放目录
$come=$text;
/*水平居中(换行),固定字号*/
$txt_max_width = intval(0.8*$pic_width);
$content = "";
for ($i=0;$i<mb_strlen($come);$i++) {
$letter[] = mb_substr($come, $i, 1);
}
// var_dump($letter);die;
foreach ($letter as $l) {
$teststr = $content." ".$l;
$testbox = imagettfbbox($font_size,0,$font,$teststr);
// var_dump($testbox);die;
// 判断拼接后的字符串是否超过预设的宽度
if (($testbox[2] > $txt_max_width) && ($content !== "")) {
$content .= "\n";
}
$content .= $l;
}
$test = explode("\n",$content);
// var_dump($test);die;
// $fbox = imagettfbbox(10,0,$font,$come);
// echo 1;die;
$txt_width = $testbox[2]-$testbox[0];
$txt_height = $testbox[0]-$testbox[7];
$y = ($pic_hight * 0.8)-((count($test)-1)*$txt_height); // baseline of text at 90% of $img_height
// var_dump($txt_height);die;
// imagettftext($im,$font_size,0,$x,$y,$col,$font,$content); //写 TTF 文字到图中
foreach ($test as $key => $value) {
$textbox = imagettfbbox($font_size,0,$font,$value);
$txt_height = $textbox[0]-$textbox[7];
$text_width = $textbox[2]-$textbox[0];
$x = ($pic_width - $text_width) / 2;
imagettftext($im,$font_size,0,$x,$y,$col,$font,$value);
$y = $y+$txt_height+2; // 加2为调整行距
}
return $im;
}
这里需要说明一下代码中的字体,因为gd库生成文字图片时需要指定一种字体。所有想要什么字体,需要到网上下载才能使用。
到此小功能都已解决。接下来就是如何去拼接这个数组,如何去遍历这个数组,生成想要的结果图片。
功能实现
懒得再去写代码,这里从项目和中拷贝生成图片部分的代码。
try {
/*
* 计算长图高
* $this->PicALLHeight($for_length,468) => 点位图总高(468为图片压缩后宽度);
* (count($for_length,1)-count($for_length))*100 =>点位图数量乘于100;100为点位文字图高,相减为只求二维的数量。
* count($image_arr)*100 =>排期数量乘于100;100为排期文字图高.
* PicALLHeight($media_for_length,234) =>媒体图总高(234为图片压缩后宽度) ;
*/
$pic_tall = Common::PicALLHeight($for_length,468)+(count($for_length,1)-count($for_length))*100+count($image_arr)*100+Common::PicALLHeight($media_for_length,234);
// 创建长图
$temp = imagecreatetruecolor(468,$pic_tall);
$color = imagecolorAllocate($temp,255,255,255); //分配一个白色底色
imagefill($temp,0,0,$color);
$add_tall = 0;
// $ymd = date('-Ymd',time()); //排期命名+日期
// var_dump($image_arr);die;
foreach ($image_arr as $k => $v) {
// 创建排期名称文字图片
// $schedule_info=Schedule::CheckSchedule($k);
// var_dump($schedule_info);die;
$font_pic=Common::FontToPic($copy_schedule_name[$k],18);
// header('Content-Type: image/jpeg');
// imagejpeg($font_pic);die;
imagecopymerge($temp,$font_pic,0,$add_tall,0,0,468,100,100);
$add_tall += 100;
foreach ($v as $ke => $val) {
// 创建大媒体图片
list($open_pic,$width,$height) = Common::PicCompress($all_media_arr[$ke],234);
imagecopymerge($temp,$open_pic,117,$add_tall,0,0,$width,$height,100);
$add_tall += $height;
foreach ($val as $key => $value) {
$font_pic=Common::FontToPic($key,10.5);
imagecopymerge($temp,$font_pic,0,$add_tall,0,0,468,100,100);
$add_tall += 100;
list($open_pic,$width,$height) = Common::PicCompress($value,468);
imagecopymerge($temp,$open_pic,0,$add_tall,0,0,$width,$height,100);
// echo $width.'|||||';
$add_tall += $height;
}
}
}
// header("Content-type: image/jpeg");
// imagejpeg($temp);
// die;
$savepath=Yii::$app->basePath.'/uploads/longpic/'.date('Ym',time()).'/';
if(!is_dir($savepath)){
mkdir($savepath,0777,true);
}
$savafile = $savepath.$pic_schedule_id.time().'.jpg';
// echo $savepath;die;
imagejpeg($temp,$savafile,100);
}catch(\Exception $e) {
//捕获生成长图时的任意异常,异步返回标识。
$this->returnJson(false,'图片生成出错','2');
}
到此拼接图片功能就完成了。需要注意imagecopymerge这个函数。这是一个合并图片的函数,需要指定原图的宽高和结果图的宽高,复制的起始坐标。函数的必须参数比较多。需要弄懂每个参数的用法。
imagejpeg函数用于生成图片。有三个参数,第一个参数为图片对象,第二个参数为生成的图片名称,第三个参数为图片的质量。0–100,值越大质量越高。
在开发工程中,可能思路不会那么清晰。这需要不断的取验证自己的想法。整体流程没问题,但是在这个过程中,可能需要反复的去调整。
效果图如下
文章没有很详细的去介绍用到的每个函数。但是对gd库这几个函数有研究,加上代码中的注释应该都可以看懂。主要还是思路,思路正确了,实现的方法有很多种。