在yii2應用中,使用imagine庫生成分享圖實戰
阿新 • • 發佈:2022-12-09
這個需求現在特別常見,比如生成小程式分享圖、生成朋友圈分享圖等等,一般是文字 + 二維碼 + 背景模板。今天我們使用imagine來完成這件事情,並作用於網站的面試題模組。
我規劃的分享圖佈局如下
在這裡面題目標題、日期和二維碼是需要替換的,其他部分均可以做到背景圖中。
準備階段
為了讓這件事情能實現,我們需要準備一些東西
我們知道imagine的安裝可以使用composer,我們首先安裝它
安裝完以後你可以在yii2的 vendor/imagine/imagine 內找到它,如果沒有請檢查環境的composer環境(儘量不要使用映象,否則可能出現無法獲取最新版本問題)。
因為imagine需要的PHP環境是5.3+,因此只要你的yii2可以執行,imagine一般都是沒問題的。
在這張分享圖上我計劃放一個二維碼,它含有的是此次面試題的URL,這樣分享到朋友圈或群的時候,大家通過長按二維碼就能訪問到,為了每個圖片只反映一個主題,關於面試題的訂閱等需求均放到目標頁面,分享圖只做一個事情。
在yii2中生成二維碼有很成熟的庫 —— qrcode-library ,使用它可以生成不同尺寸、內容及樣式的二維碼,強烈推薦。
qrcode-library的安裝也非常簡單,依然是composer。
同樣安裝後我們應該在 vendor/2amigos/qrcode-library 資料夾內找到它。
為了讓樣式好看,我決定找一個字型,然後寫到分享圖上,網上字型下載的網站太多太多,我選擇下載微軟雅黑,再熟悉不管的字型了。
寫入標題
通過上面都準備完成,我們接下來思考分享圖的生成邏輯,其實就是貼水印,文字的水印、圖片水印,就是這樣。我做的圖片背景模板尺寸是500*750,看下圖。
當然這張圖我們後面還要寫話並填充內容,圖中白色的矩形區域我計劃防止標題內容,因為面試題標題長度一般都不長,我預留的3行的高度足夠用了。
現在假設標題為 【請使用PHP迴圈出本週一到本週日】,我們需要開啟背景圖然後做手腳,開始實際編碼。
通過dump可以看到正確打開了。
imagine和image物件都正常輸出了。
接下來的主要工作就是寫入標題
寫入標題等價於為一個圖片新增文字水印,這裡面涉及的問題如下
文字內容是什麼
用什麼字型
字號大小
字型顏色
從哪個座標開始
注:imagine和笛卡爾座標系不同,詳情見 文件
先說說字型庫,雖然微軟雅黑是win系列的標配,但是伺服器上不一定有,我使用的是centos系統,需要下載和指定,在上一部分我們已經下載了字型,現在我將其放到yii2應用的fonts資料夾下,這樣可以通過如下程式碼訪問它。
除非你的頁面需要此字型,否則不推薦將字型放到web目錄下,這樣可以有效防止其他人通過瀏覽器訪問。
接下來我們初步實現一下,接著上面的程式碼。
在講解上面程式碼之前,我們先看看結果。
得到了我們要的結果,接下來說說邏輯。
在 imagine 中如果為一個圖片增加文字水印,它屬於繪製功能,要呼叫draw的text方法,我們先看看這個方法的宣告。
這就是你剛剛程式碼中的 $image->draw()->text(); 部分。
它有幾個重要的引數,比如文字內容、字型、起始座標,內容弧度等。
因此我們做的一起就是為這個函式準備引數值,內容、字型、座標。
這些類都是為了最終呼叫 text 方法做準備的。
比如我們聽過RGB類定義顏色類
比如我們需要通過font方法得到字型類物件
比如我們通過Point來定義座標物件
好了,雖然到現在我們實現了標題的寫入,但是位置和大小都不理想,接下來優化。
字型大小
座標位置
之前是12,現在我們設定為24,座標從[0,0]改為[44,230]
看下效果
很高興,現在為止大小和位置都調整的不錯,但是新的問題來了,我的標題內容超過了一行,其他部分沒有換行而是被切掉了,怎麼辦???
內容超過邊界問題的處理
怎麼處理這個問題?在imagine 1.0.0+已經提供了一個自動換行的函式,你只需要升級版本即可,用法非常簡單,如下。
但是,在此文章並不適用,wrapText對中文的支援並不好,因此我們需要另想辦法,雖然如此,我們還是有必要看看wrapText的實現原理。
**那對中文怎麼辦?**沒關係,從官方wrapText的方法我們可以改造出子的方法,只不過每個詞的話不不再是空格,我進行了如下改造。
思路就是首先獲得整個字串的長度N,然後從0到N-1遍歷,得到每個字(中英文),然後將這些字放到一個測試行testLine中,並通過$font->box方法得到測試行的寬度,超過了我們最大寬度則重新設定測試行,一次又一次,最後lines數組裡就是每一行,且他們都沒有超過邊界maxWidth。
最後使用換行符再將lines陣列拼湊回字符串,ok,看效果。
更好的行間距
剛剛我們解決了溢位問題,但是現在每行的間距太小了,這樣大大影響了體驗,這小節我們將做出一個合適的行間距,但是你知道當我們將文字劃到圖片的時候,是無法設定行邊距的。
這一切要從座標開始研究。
因此我計劃取消上面將lines陣列重新拼湊成字串的程式碼,保留每一行,然後指定每一行的具體Y座標。
行間距我留了10px,再看看效果。
通過上面的方法實現時間的寫入並不複雜,不過我決定換一個思路,時間的寫入我們並不打算直接指定座標,而是通過計算而來,這個方法將非常適合於讓一些文字居中的情形。
寫入的內容很簡單 2018-09-28,就是一個日期。
繼續擴充套件上面的程式碼,主要是計算X座標。Y座標400.
之所以這樣寫是為了讓大家熟悉Center類,我們可以將一個字型的盒子放到Center中,然後獲取中間點座標。
寫入二維碼
離成功越來越近了,接下來我們寫入二維碼,這其實是兩步。
生成二維碼
寫入二維碼到分享圖
使用 qrcode-library 生成二維碼非常簡單,我們還是先貼程式碼
在合理其實有點瑕疵,使用QrCode生成的二維碼比較簡單,但是隻支援生成Uri、伺服器檔案及流,但是無法返回資源,因此我們必須將其儲存下來後在使用 imagine 來讀取,否則就可以直接使用 imagine 的read方法了。
總之上面的程式碼通過為QrCode物件傳入URL地址來生成二維碼,同時使用writeFile將其存到伺服器。
將二維碼寫入分享圖需要使用imagine庫的paste方法,這也是我們做圖片水印的方法。
我規劃的分享圖佈局如下
在這裡面題目標題、日期和二維碼是需要替換的,其他部分均可以做到背景圖中。
準備階段
為了讓這件事情能實現,我們需要準備一些東西
- imagine圖片庫
- 二維碼生成庫
- 一個好看的字型
- 一張海報(自行用PS處理)
imagine
imagine 支援三種底層的影象處理庫(GD、Imagick和Gmagick),GD是最老的圖片庫,自然處理能力也不如另外兩種,本次我使用Imagick作為底層支援,關於PHP如何安裝Imagick擴充套件可參考 https://www.cnblogs.com/shanhubei/p/16969236.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影象上。
最後我們看到了要的效果
參考
http://www.shanhubei.com/archives/2508.html