nodejs繪製生成圖像

前言

畢設的小程序要實現分享功能,我將分享功能按照不同的頁面分成了三塊。分享的內容都是動態定義的,而且因爲微信只支持圖片分享的模式,所以這裏只能使用後端將分享的內容動態寫進圖片裏,最後上傳到七牛雲,返回一個圖片地址

閱讀器頁面分享

就是在閱讀章節的時候選擇分享,分享出去的圖片應當展示的信息包括:書籍封面、書籍名稱、章節名稱、以及部分章節內容
章節分享

書籍詳情頁分享

分享出去的圖片應當展示的信息包括:書籍封面、書籍名稱、作者、書籍類型、以及書籍簡介,這塊還在開發中,到時候再補進來

小程序首頁分享

這個功能,我想加入到後臺管理系統中,讓管理員能自定義分享的圖片,思路什麼的還在考慮中。

具體實現

說了那麼多,直接來看怎麼擼代碼吧
關於node自定義生成圖片,有一個很好的npm包,叫做node-canvas,它可以讓你在node中像在瀏覽器中一樣繪製圖像,諸如常見的drawImage、fillText、fillRect這些方法都支持。所以使用node繪製圖像就不成大問題了,但關鍵是怎麼繪製。

安裝node-canvas

# 安裝基礎依賴
# ubuntu
sudo apt-get install libcairo2-dev libjpeg-dev libpango1.0-dev libgif-dev build-essential g++
# mac OS
brew install pkg-config cairo libpng jpeg giflib

# 安裝canvas
npm install canvas

安裝出現了問題或者想要知道更多關於node-canvas的信息,可以看看它的github首頁

基礎應用

比如用node-canvas繪製一段文字

import Canvas  from 'canvas'

const canvas = new Canvas(300, 120) // 按照微信官方要求,長寬比5:4
const context = canvas.getContext('2d')
ctx.font = '14px "Microsoft YaHei"' // 統一使用微軟雅黑字體
context.fillText('Hellow', 84, 24, 204)

fillText接受四個參數,第一個是需要繪製的文本,第二個和第三個參數是定義繪製的起始位置的x和y座標,第四個參數用來控制繪製文本的最大寬度maxWidth。想要繪製好一張圖片,這些位置信息都是要自己計算的哦!

drawImage的說明

在繪製書籍封面圖片的時候就會使用到drawImage方法,但是node-canvas的drawImage方法在傳遞圖片參數的時候只接收Buffer類型,所以這裏就涉及到如何將網絡圖片(從數據庫中取出的封面圖片地址,例如:)轉換成Buffer類型。
我們使用https的內置包(如果你請求的圖片地址是http的請使用http的內置包)請求封面圖片地址,在拼裝請求數據之後使用Buffer.concat方法將其裝換成Buffer類型。
另外應爲是在koa的router中寫的代碼,所以面對這種異步回調類型的操作,需要使用Promise。

return new Promise((resolve, reject) => {
  // 將封面圖片轉成buffer格式,用於canvas繪製圖片
  https.get(thisBook.img_url, imgRes => {
    let chunks = [] // 用於保存網絡請求不斷加載傳輸的緩衝數據
                 let size = 0 // 保存緩衝數據的總長度
    imgRes.on('data', chunk => {
      /**
        * 在進行網絡請求時,會不斷接收到數據(數據不是一次性獲取到的)
        * node會把接收到的數據片段逐段的保存在緩衝區(Buffer)
        * 這些數據片段會形成一個個緩衝對象(即Buffer對象)
        * 而Buffer數據的拼接並不能像字符串那樣拼接(因爲一箇中文字符佔三個字節),
        * 如果一個數據片段攜帶着一箇中文的兩個字節,下一個數據片段攜帶着最後一個字節,
        * 直接字符串拼接會導致亂碼,爲避免亂碼,所以將得到緩衝數據推入到chunks數組中,
        * 利用下面的node.js內置的Buffer.concat()方法進行拼接
        */
      chunks.push(chunk)
      size += chunk.length
    })
    imgRes.on('end', err => {
      if (err) {
        ctx.body = { ok: false, msg: '下載書籍封面圖片失敗' }
        reject('下載書籍封面圖片失敗')
        return
      }
      const buffer = Buffer.concat(chunks, size)
      // 判斷是否是一個buffer對象
      if (Buffer.isBuffer(buffer)) {
        // 繼續你的canvas繪畫....
      } else {
        ctx.body = { ok: false, msg: '下載書籍封面圖片失敗' }
        reject('下載書籍封面圖片失敗')
        return
      }
    })
  })
})

fillText的說明

之前提到過fillText方法可以接受一個名爲maxWidth的參數,一旦繪製的文本超過這個寬度就會自動換行。本來這個設計是很好的,但是實際使用過程中就會發現,自動換行的行高很大(大到影響美觀)。而且通過context.font也沒辦做到控制行高,因爲line-height和font-size在context.font中是同一個參數。
既然這樣,又只能自己想辦法了。行高沒法控制,就只能每次繪製一行文本,然後使用繪製的時候傳入的起始位置的y座標來控制兩行之間的間距。

// 首先對從數據庫中讀取到的文本去掉換行符和製表符這些
const noSpaceContent = thisBook.chapters[0].content.replace(/(\n|\t|\r)/g, '')
// 使用canvas的measureText方法得到一箇中文的寬度,然後用最大寬度除以它得到一行最多可容納的字符數
const oneTextWidth = context.measureText('測').width
const oneLineMaxTextNumber = Math.ceil(204 / oneTextWidth)
// 小說描述最大行數
let maxLineNumber = 4
// 當前行數
let current = 4
while (current > 0) {
  let tmpText = noSpaceContent.substring(oneLineMaxTextNumber * (maxLineNumber - current), oneLineMaxTextNumber * (maxLineNumber + 1 - current))
  // 最後一行文字顯示省略號
  if (current === 1) {
    tmpText = tmpText.substring(0, tmpText.length - 2) + '...'
  }
  context.fillText(tmpText, 84, 62 + (maxLineNumber - current) * 15, 204)
  current --
}

圖片上傳七牛雲

我這裏使用的qn的包,大家去官網看看應該就知道怎麼用。爲了防止自己忘記,這裏貼下代碼。
首先將配置寫到config.js裏

const config = {
    accessKey: 'xxx', //七牛賬號的key值,https://portal.qiniu.com/user/key
    secretKey: 'xxx',
    bucket: 'upload', //空間名稱
    isUseHttps: true, //配置使用https,並且對應到正確的區域,詳情請查考https://github.com/gpake/qiniu-wxapp-sdk/blob/master/README.md
}

module.exports = config;

然後初始化七牛配置

import config from '../config'
import qn from 'qn'

// qiniu上傳設置
const client = qn.create({
  accessKey: config.accessKey,
  secretKey: config.secretKey,
  bucket: 'upload',
  origin: 'https://fs.andylistudio.com',
});

七牛雲允許直接上傳本地圖片,也可以直接是圖片的buffer對象,我們使用node-canvas提供的toBuffer方法直接將我們繪製的canvas轉成buffer對象然後上傳

// 上傳圖片到七牛雲
client.upload(canvas.toBuffer(), {key: 'mbook/share/' + uuid.v1() + '.png' }, function (err, result) {
  if (err) {
    ctx.body = { ok: false, msg: '分享圖片導出失敗' }
    reject('分享圖片導出失敗')
    return
  }
  ctx.body = { ok: true, msg: '分享圖片導出成功', url: result.url }
  resolve(next())
})

效果圖

是不是感覺還可以,哈哈~,完整代碼請參考這裏
效果圖

結束

原文請查看 https://andyliwr.github.io/2018/04/23/node_canvas_usage/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章