【實戰教程】在小程式中快速生成分享海報

知曉雲發表於2018-12-11

在小程式中生成分享海報是一個很常見的需求,目前主要有以下兩種做法:

直接由前端生成,使用小程式提供的 canvas API

由後端知曉雲「雲函式」生成,前端再獲取

本文將介紹通過知曉雲「雲函式」來生成分享海報的功能,並使用 webpackmincloud 將程式碼打包上傳到知曉雲。

技術棧:

程式碼打包工具: webpack@4.22.0

部署工具:mincloud@1.0.4

圖片處理:gm@1.23.1

其他:知曉雲 SDKimageMagick(雲函式內建)


一、專案搭建

專案檔案結構:

  gm-draw-image
  ├── index.js
  ├── package.json
  ├── src
  │   └── index.js
  ├── webpack.config.js
  └── yarn.lock
複製程式碼

專案搭建與雲函式程式碼打包示例文件基本一致。專案搭建好後,還需要安裝以下依賴(兩種安裝方式選其一即可):

// 使用 yarn 安裝
yarn add gm mincloud

// 使用 npm 安裝
npm install --save gm mincloud
複製程式碼

修改 deploy 指令碼,如下:

// package.json
...
"scripts": {
   "build": "webpack --mode production",
   "predeploy": "npm run build""deploy": "mincloud deploy gm-draw-image ../"
},
...
複製程式碼

最終我們會使用以下兩個命令來部署和測試:
npm run deploy  // 部署到知曉雲
mincloud invoke gm-draw-image  // 測試已經部署到知曉雲上的雲函式
複製程式碼

二、生成海報

這裡分為 4 個步驟:

  • 下載海報需要的資源

  • 圖片 / 文字處理

  • 海報繪製

  • 將繪製完成的海報上傳到知曉雲


使用知曉雲 Node SDK 的 request 方法將資源下載下來(不能直接使用其他第三方 request 包):

BaaS.request.get(url)
  .then(res => {})
  .catch(err => {})

複製程式碼

引入 gm 庫對圖片進行處理,雲函式沙箱環境已經內建 `imageMagick` 工具:
const gm = require('gm').subClass({ imageMagick: true })

複製程式碼

在處理文字的時候需要注意,設定字型時需選擇知曉雲已支援的字型,詳見文件文件。

另外,這裡有一個 bug:在同一個圖層中,如果填充顏色之前有填充圖片操作,那麼後續填充的顏色將會不起作用。推薦的做法是如果有需要同時填充圖片以及填充顏色,建議將這兩個操作分開處理,最後再合成一張圖。

bug 示例如下:

gm(width, height, 'none')
      .fill(bg)      // 填充了一張圖片
      .drawRectangle(`0, 0, ${width}, ${height}`)
      .fill('#fff')  // 字型顏色,這個操作不起作用
      .fontSize(20)
      .drawText(140, 170, 'mincloud')
複製程式碼

下載圖片,圖片儲存路徑需在 `/tmp/` 路徑下:
BaaS.request.get(url, {responseType: 'arraybuffer'})
  .then(res => {})
  .catch(err => {})
複製程式碼

上傳圖片到知曉雲:

var MyFile = new BaaS.File()
MyFile.upload(img, {filename: 'test.png'})
複製程式碼

完整程式碼如下:

const fs = require('fs')
const gm = require('gm').subClass({ imageMagick: true })

// 海報資源
const avatarImg = 'https://cloud-minapp-6.cloud.ifanrusercontent.com/1gQ5Mat7WAwyW1hl.jpg'
const backgroundImg = 'https://cloud-minapp-6.cloud.ifanrusercontent.com/1gQ7hCpFCK1qw8XQ.jpeg'

const MyFile = new BaaS.File()
const imageSize = {
  width: 375,
  height: 250,
}

function downloadImage(url) {
  const filename = `/tmp/${Math.random().toString().slice(2)}.jpg`
  const file = fs.createWriteStream(filename)
  return BaaS.request.get(url, {responseType: 'arraybuffer'}).then(res => {
      file.write(res.data)
      file.end()
      return filename
  })
}

function uploadImage(buffer) {
  return MyFile.upload(buffer, {filename: Math.random().toString().slice(2) + '.png'}) 
}

function drawText(filename) {
  return new Promise((resolve, reject) => {
    gm(imageSize.width, imageSize.height, 'none')
      // 設定字型以及文字大小,這裡只能設定雲函式已支援的字型
      .font('/usr/share/fonts/ttf-bitstream-vera/VeraMoBd.ttf')  
      .fill('#fff')
      .fontSize(20)
      .drawText(140, 170, 'mincloud')
      .fill('#fff')
      .fontSize(14)
      .drawText(30, 200, 'An easy-to-use MiniApp development tool.')
      .write(filename, function(err) {
        if (err) {
          return reject(err)
        }
        resolve(filename)
      })
  })
}

function genAvatar(avatar) {
  return new Promise((resolve, reject) => {
    gm(avatar)
      .resize(100, 100)
      .write(avatar, function(err) {
        if (err) {
          return reject(err)
        }
        resolve(avatar)
      })
  })
}

function genBackground(bg) {
  return new Promise((resolve, reject) => {
    gm(bg)
      .resize(500, 350)
      .blur(20, 2)
      .write(bg, function(err) {
        if (err) {
          return reject(err)
        }
        resolve(bg)
      })
  })
}

module.exports = function (event, callback) {
  const {width, height} = imageSize
  const job1 = downloadImage(backgroundImg).then(res => genBackground(res))
  const job2 = downloadImage(avatarImg).then(res => genAvatar(res))
  const job3 = drawText('/tmp/textLayer.png')
  Promise.all([job1, job2, job3]).then(res => {
    gm(width, height, 'none')
      .fill(res[0])
      .drawRectangle(`0, 0, ${width}, ${height}`)   // 繪製背景
      .fill(res[1])
      .drawCircle(190, 80, 190, 125)                // 繪製頭像
      .fill(res[2])
      .drawRectangle(`0, 0, ${width}, ${height}`)   // 繪製文字
      .toBuffer('PNG', function (err, buffer) {
        if (err) {
          return callback(err)
        }
        uploadImage(buffer).then((res) => {
          console.log('success')
          callback(null, res.data.file_link)
        }).catch(err => {
          callback(err)
        })
      })
  })
}

複製程式碼

三、部署並測試

npm 一樣,部署前需要先登入,請參照文件配置。

使用以下命令即可將雲函式部署到知曉雲:

npm run deploy
複製程式碼

執行結果如下:

【實戰教程】在小程式中快速生成分享海報

使用以下的命令來測試:

mincloud invoke image-crawler
複製程式碼

執行結果如下:

【實戰教程】在小程式中快速生成分享海報

上傳到知曉雲的圖片如下:

【實戰教程】在小程式中快速生成分享海報

生成的海報效果圖:

【實戰教程】在小程式中快速生成分享海報

素材原圖:

【實戰教程】在小程式中快速生成分享海報

四、參考文件

知曉雲開發文件:doc.minapp.com/
gm 官方文件:aheckmann.github.io/gm/docs.htm…


五、原始碼

倉庫地址:github.com/ifanrx/gm-d…

相關文章