这个需求如今特别常见,好比生成小程序分享图、生成朋友圈分享图等等,通常是文字 + 二维码 + 背景模板。今天咱们使用imagine来完成这件事情,并做用于网站的面试题模块。php
我规划的分享图布局以下html
在这里面题目标题、日期和二维码是须要替换的,其余部分都可以作到背景图中。web
为了让这件事情能实现,咱们须要准备一些东西面试
imagine 支持三种底层的图像处理库(GD、Imagick和Gmagick),GD是最老的图片库,天然处理能力也不如另外两种,本次我使用Imagick做为底层支持,关于PHP如何安装Imagick扩展可参考 www.cnblogs.com/aini521521/…算法
咱们知道imagine的安装可使用composer,咱们首先安装它小程序
composer require imagine/imagine
复制代码
安装完之后你能够在yii2的 vendor/imagine/imagine 内找到它,若是没有请检查环境的composer环境(尽可能不要使用镜像,不然可能出现没法获取最新版本问题)。centos
由于imagine须要的PHP环境是5.3+,所以只要你的yii2能够运行,imagine通常都是没问题的。数组
在这张分享图上我计划放一个二维码,它含有的是这次面试题的URL,这样分享到朋友圈或群的时候,你们经过长按二维码就能访问到,为了每一个图片只反映一个主题,关于面试题的订阅等需求均放到目标页面,分享图只作一个事情。浏览器
在yii2中生成二维码有很成熟的库 —— qrcode-library ,使用它能够生成不一样尺寸、内容及样式的二维码,强烈推荐。服务器
qrcode-library的安装也很是简单,依然是composer。
composer require 2amigos/qrcode-library
复制代码
一样安装后咱们应该在 vendor/2amigos/qrcode-library 文件夹内找到它。
为了让样式好看,我决定找一个字体,而后写到分享图上,网上字体下载的网站太多太多,我选择下载微软雅黑,再熟悉无论的字体了。
经过上面都准备完成,咱们接下来思考分享图的生成逻辑,其实就是贴水印,文字的水印、图片水印,就是这样。我作的图片背景模板尺寸是500*750,看下图。
固然这张图咱们后面还要写话并填充内容,图中白色的矩形区域我计划防止标题内容,由于面试题标题长度通常都不长,我预留的3行的高度足够用了。
如今假设标题为 【请使用PHP循环出本周一到本周日】,咱们须要打开背景图而后作手脚,开始实际编码。
use Imagine\Imagick\Imagine;
public function actionShare($id){
$imagine = new Imagine();
$image = $imagine->open(Yii::getAlias("@webroot")."/images/task-bg.jpg");
//VarDumper::dump($image,10,true);
}
复制代码
经过dump能够看到正确打开了。
imagine和image对象都正常输出了。
接下来的主要工做就是写入标题
写入标题等价于为一个图片添加文本水印,这里面涉及的问题以下
注:imagine和笛卡尔坐标系不一样,详情见 文档
先说说字体库,虽然微软雅黑是win系列的标配,可是服务器上不必定有,我使用的是centos系统,须要下载和指定,在上一部分咱们已经下载了字体,如今我将其放到yii2应用的fonts文件夹下,这样能够经过以下代码访问它。
Yii::getAlias('@app')."/fonts/yahei.ttf";
复制代码
除非你的页面须要此字体,不然不推荐将字体放到web目录下,这样能够有效防止其余人经过浏览器访问。
接下来咱们初步实现一下,接着上面的代码。
use Imagine\Imagick\Imagine;
use Imagine\Image\Palette\RGB;
use Imagine\Image\Point;
public function actionShare($id){
$imagine = new Imagine();
$image = $imagine->open(Yii::getAlias("@webroot")."/images/task-bg.jpg");
$palette = new RGB();
$color = $palette->color("000000");
$point = new Point(0,0);
$font = $imagine->font(Yii::getAlias('@app')."/fonts/yahei.ttf",12,$color);
$image->draw()->text("请使用PHP循环出本周一到本周日",$font,$point);
$image->show('jpg');
}
复制代码
在讲解上面代码以前,咱们先看看结果。
获得了咱们要的结果,接下来讲说逻辑。
在 imagine 中若是为一个图片增长文本水印,它属于绘制功能,要调用draw的text方法,咱们先看看这个方法的声明。
text(string $string, AbstractFont $font, PointInterface $position, int $angle = 0, int $width = null)
复制代码
这就是你刚刚代码中的 $image->draw()->text(); 部分。
它有几个重要的参数,好比文字内容、字体、起始坐标,内容弧度等。
所以咱们作的一块儿就是为这个函数准备参数值,内容、字体、坐标。
use Imagine\Image\Palette\RGB;
use Imagine\Image\Point;
复制代码
这些类都是为了最终调用 text 方法作准备的。
好比咱们听过RGB类定义颜色类
$palette = new RGB();
$color = $palette->color("000000");
复制代码
好比咱们须要经过font方法获得字体类对象
$font = $imagine->font(Yii::getAlias('@app')."/fonts/yahei.ttf",12,$color);
复制代码
好比咱们经过Point来定义坐标对象
$point = new Point(0,0);
复制代码
好了,虽然到如今咱们实现了标题的写入,可是位置和大小都不理想,接下来优化。
以前是12,如今咱们设置为24,坐标从[0,0]改成[44,230]
$point = new Point(44,230);
$font = $imagine->font(Yii::getAlias('@app')."/fonts/yahei.ttf",24,$color);
复制代码
看下效果
很高兴,如今为止大小和位置都调整的不错,可是新的问题来了,个人标题内容超过了一行,其余部分没有换行而是被切掉了,怎么办???
怎么处理这个问题?在imagine 1.0.0+已经提供了一个自动换行的函数,你只须要升级版本便可,用法很是简单,以下。
$font = $imagine->font(Yii::getAlias('@app')."/fonts/yahei.ttf",24,$color);
$text = $font->wrapText("请使用PHP循环出本周一到本周日",400);
复制代码
wrapText方法的第二个参数表明文本最大长度,超过了即为换行,而后将wrapText做用后的$text再传给 image->draw()->text(); 便可。
可是,在此文章并不适用,wrapText对中文的支持并很差,所以咱们须要另想办法,虽然如此,咱们仍是有必要看看wrapText的实现原理。
// vendor/imagine/imagine/src/Image/FontInterface.php:59
public function wrapText($string, $maxWidth, $angle = 0){
$words = explode(' ', $string);
foreach ($words as $word) {
if ($currentLine === null) {
$currentLine = $word;
} else {
$testLine = $currentLine . ' ' . $word;
$testbox = $this->box($testLine, $angle);
if ($testbox->getWidth() <= $maxWidth) {
$currentLine = $testLine;
} else {
$lines[] = $currentLine;
$currentLine = $word;
}
}
}
.....
return implode("\n", $lines);
}
复制代码
你看到了wrapText的实现是经过空格来实现单词的划分,所以这个算法只适用于英文,最后经过计算宽度,经过换行符实现最后效果。
**那对中文怎么办?**不要紧,从官方wrapText的方法咱们能够改造出子的方法,只不过每一个词的话不再也不是空格,我进行了以下改造。
$lines = array();
$maxWidth = 420;
$currentLine = null;
$text = "请使用PHP循环出本周一到本周日";
for($i = 0;$i < mb_strlen($text,'UTF-8');$i++){
$word = mb_substr($text,$i,1,'UTF-8');
if ($currentLine === null) {
$currentLine = $word;
} else {
$testLine = $currentLine.$word;
$testbox = $font->box($testLine, 0);
if ($testbox->getWidth() <= $maxWidth) {
$currentLine = $testLine;
} else {
$lines[] = $currentLine;
$currentLine = $word;
}
}
}
if ($currentLine !== null) {
$lines[] = $currentLine;
}
$text = implode("\n", $lines);
复制代码
思路就是首先得到整个字符串的长度N,而后从0到N-1遍历,获得每一个字(中英文),而后将这些字放到一个测试行testLine中,并经过$font->box方法获得测试行的宽度,超过了咱们最大宽度则从新设置测试行,一次又一次,最后lines数组里就是每一行,且他们都没有超过边界maxWidth。
最后使用换行符再将lines数组拼凑回字符串,ok,看效果。
刚刚咱们解决了溢出问题,可是如今每行的间距过小了,这样大大影响了体验,这小节咱们将作出一个合适的行间距,可是你知道当咱们将文本划到图片的时候,是没法设置行边距的。
这一切要从坐标开始研究。
所以我计划取消上面将lines数组从新拼凑成字符串的代码,保留每一行,而后指定每一行的具体Y坐标。
$height = $font->box($model->title)->getHeight();// 得到字的高度。$model->title 就是输出的内容
$lines = array();
$currentLine = null;
for($i = 0;$i < mb_strlen($model->title,'UTF-8');$i++){
$word = mb_substr($model->title,$i,1,'UTF-8');
if ($currentLine === null) {
$currentLine = $word;
} else {
$testLine = $currentLine.$word;
$testbox = $font->box($testLine, 0);
if ($testbox->getWidth() <= 420) {
$currentLine = $testLine;
} else {
$lines[] = $currentLine;
$currentLine = $word;
}
}
}
if ($currentLine !== null) {
$lines[] = $currentLine;
}
// 得到lines数组
foreach($lines as $key=>$value){
$point = new Point(40,($key == 0 ? 230 : (230 + ($height + 10)*$key)));
$image->draw()->text($value,$font,$point,0);
}
$image->show('jpg');
复制代码
行间距我留了10px,再看看效果。
经过上面的方法实现时间的写入并不复杂,不过我决定换一个思路,时间的写入咱们并不打算直接指定坐标,而是经过计算而来,这个方法将很是适合于让一些文字居中的情形。
写入的内容很简单 2018-09-28,就是一个日期。
继续扩展上面的代码,主要是计算X坐标。Y坐标400.
use Imagine\Image\Point\Center;
$dateText = date('Y-m-d',$model->publish_date);
$dateFont = $imagine->font(Yii::getAlias('@app')."/fonts/yahei.ttf",12,$color);
$dateBox = $dateFont->box($dateText);
$dateCenterPosition = new Center($dateBox);
$image->draw()->text($dateText,$dateFont,new Point(($image->getSize()->getWidth()/2 - $dateCenterPosition->getX()),420));
复制代码
之因此这样写是为了让你们熟悉Center类,咱们能够将一个字体的盒子放到Center中,而后获取中间点坐标。
离成功愈来愈近了,接下来咱们写入二维码,这实际上是两步。
使用 qrcode-library 生成二维码很是简单,咱们仍是先贴代码
use Da\QrCode\QrCode;
$qrCode = (new QrCode(Yii::$app->urlManager->createAbsoluteUrl(['/task/detail','id'=>$id])))->setSize(180)->setMargin(10);
$path = Yii::getAlias('@webroot').'/uploads/tmp/'.Yii::$app->security->generateRandomString().'.jpg';
$qrCode->writeFile($path);
复制代码
在合理其实有点瑕疵,使用QrCode生成的二维码比较简单,可是只支持生成Uri、服务器文件及流,可是没法返回资源,所以咱们必须将其保存下来后在使用 imagine 来读取,不然就能够直接使用 imagine 的read方法了。
总之上面的代码经过为QrCode对象传入URL地址来生成二维码,同时使用writeFile将其存到服务器。
##写入二维码到分享图
将二维码写入分享图须要使用imagine库的paste方法,这也是咱们作图片水印的方法。
$water = $imagine->open($path);
$image->paste($water,new Point(150,520));
复制代码
经过 open 方法读取一个文件返回image对象,经过paste将其贴到$image图像上。
最后咱们看到了要的效果
分享图的细节太多太多,这一切还须要优化,本篇但愿对你能起到抛砖引玉的做用,若是你有好的库也欢迎留言。