图解112-蛇蛋图:哎呀,太不小心了.PHP图片处理分析问题!

在yii2应用中,使用imagine库生成分享图实战。

这个需求现在特别常见,比如生成小程序分享图、生成朋友圈分享图等等,一般是文字 + 二维码 + 背景模板。今天我们使用imagine来完成这件事情,并作用于网站的面试题模块。

请=加=我=们=的=V+信【wqv 370】关注免费资料实力解答
请=加=我=们=的=V+信【wqv 370】关注免费资料用心分析

111期:[猴羊] 开 :羊16 中
110期:[龙猪] 开 :猪36 中
109期:[羊兔] 开 :羊04 中
108期:[蛇虎] 开 :蛇30 中
107期:[牛马] 开 :牛46 中

在这里面题目标题、日期和二维码是需要替换的,其他部分均可以做到背景图中。

准备阶段

为了让这件事情能实现,我们需要准备一些东西

  • 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图像上。

最后我们看到了要的效果

小结

分享图的细节太多太多,这一切还需要优化,本篇希望对你能起到抛砖引玉的作用,如果你有好的库也欢迎留言。

原文地址:https://www.cnblogs.com/phpyes/p/9727216.html

时间: 2024-10-14 10:30:37

图解112-蛇蛋图:哎呀,太不小心了.PHP图片处理分析问题!的相关文章

112+蛇蛋图:哎呀,太不小心了、谈谈Yii2生命周期的超前概念!!

请=加=我=们=的=V+信[wqv 370]关注免费资料实力解答112期已解答,请加我们的导师免费获取答案,期期免费获取,不收任何费用赶紧行动起来111期:[猴羊] 开 :羊16 中110期:[龙猪] 开 :猪36 中109期:[羊兔] 开 :羊04 中108期:[蛇虎] 开 :蛇30 中 Yii2基本概念之--生命周期(LifeCycle) 论Yii2生老病死,一年有春夏秋冬四季演替,封建王朝有兴盛.停滞.衰亡的周期律--"其兴也勃焉,其亡也忽焉".换句话说,人,季节,王朝等等这些世

112期/蛇蛋图:哎呀,太不小心了..

112期蛇蛋图:哎呀,太不小心了.. 已解 下发图片微信扫一扫加好友!领取三肖!!! restful 访问了未定义的路径,怎么设置统一跳转到自定义的方法而不直接报错? 在restful开发遇到一一些问题: 如果我访问的路径故意写错 : xxx.com/login123 ,则如下提示: 百度了一下,在components里面添加 'errorHandler' => [ 'errorAction' => 'default/error', ], 而且default/error方法写了  甚至路由也配

107蛇蛋图:闲著也是闲著

大师解答,最精准的数据 最权威的借料平台 免费给料,免费 免费 免费 重要的事情说三次!!! 配置api 为什么就访问不通呢 求各位了 mian.php 目录结构 原文地址:https://www.cnblogs.com/phpyes/p/9674655.html

QT模态对话框用法(在UI文件中设置Widget背景图,这个图是一个带阴影边框的图片——酷)

QT弹出模态对话框做法: 1.新建UI文件时,一定要选择基类是QDialog的,我的选择是:Dialog without Buttons(),如下图: 2.然后在使用的时候: MyDialog dlg(this); dlg.exec(); 如果不加this,则会在任务管理器里面产生一个新的EXE. 3.如果对话框的标题是自定义,不想使用系统的标题,这时候需要在代码中加入: setWindowFlags(Qt::Dialog | Qt::FramelessWindowHint ); setAttr

CSS Sprites技术(将背景图整合到一张图中,再利用CSS背景图片定位到要显示的位置)

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Typ

切图崽的自我修养-优化图片加载流程

前言 优化! 又是优化! 切图崽们作为整个web应用的纽带,连接着用户行为和机器性能. 而优化的最终意义,在于在这两者之间取得一个最佳的平衡点. 对于图片资源的加载来说,更是如此. 今天我们就来简单说说,项目开发中常见的图片加载优化方式. 预加载 1.遮罩大法 我们经常用jquery, jquery中$(function){})实际上是DOMContentLoaded事件完成的回调,只是完成了DOM树的构建. 诸如Css的渲染以及页面内图片等资源的下载不一定完成了.所以如果此时呈现页面,页面会非

图解六大UML类图关系

在学习UML类图的过程中,UML类图关系是必须要掌握的问题,UML定义的关系主要有六种:依赖.类属.关联.实现.聚合和组合.下面对其定义和表示方法逐一说明. UML类图关系简介 依赖(Dependency):元素A的变化会影响元素B,但反之不成立,那么B和A的关系是依赖关系,B依赖A:类属关系和实现关系在语义上讲也是依赖关系,但由于其有更特殊的用途,所以被单独描述.UML中用带箭头的虚线表示Dependency关系,箭头指向被依赖元素. 类属(Generalization):通常所说的继承(特殊

Linux下PHP自动生成文章预览图,html转换成各种格式图片、PDF-----转自phpboy的文章

原文地址:http://www.phpboy.net/linux/575.html 用WordPress建立博客站点,选择了一套可以显示文章缩略图的模板,几经折腾将原有模板改得面目全非,最后还是直接上线吧,不想折腾了. 站点上线没几天,在公司做项目时,对图片做了一个放大的JS,自己博客也加上点击缩略图查看原图的功能,然后迅速的加了. 过了几天,突发奇想,想做文章预览图,即点击缩略图查看文章预览图,也就是你们现在首页和文章列表页看到的那个功能. 不费话了,不知道什么时候又要折腾... Linux下

爱留图 - 一个定期开设专栏活动的图片收集网站诞生。

本章和大家分享的是一个自制的图片收集网站:爱留图:本章不打算分享什么技术知识点,而分享的主要内容有网站的创立的需求,现阶段采用的技术架构,服务器配置等信息:在站点服务构建时用到的部分技术,知识点,以及遇到的问题会在后面分不同的章节+不同的小节来讲解,乐于和大家分享自己的经验:不知道本篇文章发表出去后,dudu的编辑人员是不是会认为是一篇广告博文而拒绝掉,当然个人觉得还是有不错的东西值得了解的: 技术架构 爱留图图片收集网站,主要使用的是微软推出的AspNetCore的mvc框架,她生成跨平台运行