在yii2應用中,使用imagine庫生成分享圖實戰。

這個需求如今特別常見,好比生成小程序分享圖、生成朋友圈分享圖等等,通常是文字 + 二維碼 + 背景模板。今天咱們使用imagine來完成這件事情,並做用於網站的面試題模塊。php

我規劃的分享圖佈局以下html

在這裏面題目標題日期二維碼是須要替換的,其餘部分都可以作到背景圖中。web

準備階段

爲了讓這件事情能實現,咱們須要準備一些東西面試

  • imagine圖片庫
  • 二維碼生成庫
  • 一個好看的字體
  • 一張海報(自行用PS處理)

imagine

imagine 支持三種底層的圖像處理庫(GD、Imagick和Gmagick),GD是最老的圖片庫,天然處理能力也不如另外兩種,本次我使用Imagick做爲底層支持,關於PHP如何安裝Imagick擴展可參考 https://www.cnblogs.com/aini5...算法

咱們知道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圖像上。

最後咱們看到了要的效果

小結

分享圖的細節太多太多,這一切還須要優化,本篇但願對你能起到拋磚引玉的做用,若是你有好的庫也歡迎留言。

阿北哥ya https://nai8.me
相關文章
相關標籤/搜索