在yii2应用中,使用imagine库生成分享图实战。
这个需求现在特别常见,比如生成小程序分享图、生成朋友圈分享图等等,一般是文字 + 二维码 + 背景模板。今天我们使用imagine来完成这件事情,并作用于网站的面试题模块。
我规划的分享图布局如下
在这里面题目标题、日期和二维码是需要替换的,其他部分均可以做到背景图中。
准备阶段
为了让这件事情能实现,我们需要准备一些东西
- imagine图片库
- 二维码生成库
- 一个好看的字体
- 一张海报(自行用PS处理)
imagine
imagine 支持三种底层的图像处理库(GD、Imagick和Gmagick),GD是最老的图片库,自然处理能力也不如另外两种,本次我使用Imagick作为底层支持,关于PHP如何安装Imagick扩展可参考 https://www.cnblogs.com/aini521521/p/8398770.html
我们知道imagine的安装可以使用composer,我们首先安装它
composer require imagine/imagine
安装完以后你可以在yii2的 vendor/imagine/imagine 内找到它,如果没有请检查环境的composer环境(尽量不要使用镜像,否则可能出现无法获取最新版本问题)。
因为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图像上。
最后我们看到了要的效果
小结
分享图的细节太多太多,这一切还需要优化,本篇希望对你能起到抛砖引玉的作用,如果你有好的库也欢迎留言。
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
业务代码解构利器--SWAK
作者:闲鱼技术-紫思 简介 业务的不断发展、商品类型的不断增多、不断添加的业务需求使得闲鱼的代码出现“bad smell”——平台代码和业务代码耦合严重难以分离;业务和业务之间代码交织缺少拆解。这也是行业中的通病。为解决此类问题,闲鱼自研了一套技术框架——SWAK。本文带大家一起看看SWAK是怎么解构闲鱼代码的。 SWAK是Swiss Army Knife的简称,众所周知,瑞士军刀是一款小巧灵活、适用于多种场景的工具。在闲鱼服务端,SWAK框架也是这样一种小巧灵活、适用于多种场景的技术框架, 它所要使用的场景都具有同一个特点——多实现间的规则化执行。本文将以一个例子开篇,来详细介绍其中的概念。 多实现和规则化执行 熟悉闲鱼的朋友们应该知道,在闲鱼App里面,商品有丰富的表现形式,不妨叫做类型A、类型B和类型C,各种类型也可以有各自的子类型。每种类型的业务逻辑存在一定的共性,但是也存在部分差异——如在分享页面中,subtitle字段的展示逻辑就不尽相同: 这种单一的实现通常会被写成如下的代码: if(A类型) { if(A1类型) { doSomething1(); }else i...
- 下一篇
微服务架构下的服务关联图
在微服务架构下,服务之间的关系是非常复杂的,是一个典型的有向有环图,在一个中等规模的项目中,一般会有100多个服务,而大型项目中,则会有数百个服务。 假设我们有如下6个服务: 每个服务都指定了自己依赖的服务: AaaSvc: BbbSvc: CccSvc: DddSvc: EeeSvc: FffSvc: 我们如何把如上6个服务中跟服务AaaSvc相关的服务可视化呢?如下图所示: 要完成这样的服务关联图,需要如下几个步骤: 1、遍历指定项目下的所有服务,构造两个map,serviceToServiceMap 和 reverseServiceToServiceMap,存储所有服务的直接依赖和反向直接依赖。 public static void runService(String projectId, Map<String, Set<String>> serviceToServiceMap, Map<String, Set<String>> reverseServiceToServiceMap){ if(! (serviceToSer...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- CentOS7,8上快速安装Gitea,搭建Git服务器
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- CentOS关闭SELinux安全模块
- CentOS7设置SWAP分区,小内存服务器的救世主
- Docker安装Oracle12C,快速搭建Oracle学习环境
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- SpringBoot2全家桶,快速入门学习开发网站教程
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题