iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 19
1
Modern Web

Fabricjs 筆記系列 第 19

Day 19 - 實作 Node.js 上傳圖片及操作 Fabricjs 為圖片加上浮水印

  • 分享至 

  • xImage
  •  

Fabricjs 一個很厲害的優勢他不止能在瀏覽器的環境下執行,也能透過在 nodejs 的環境下執行,若今天我們的後端服務也剛好是使用 nodejs 的環境,就能更完美的去配合 fabricjs 前後端的圖片儲存搭配,簡單來說我們可以不用直接傳圖片給後端做儲存,可以直接透過後端去轉換並儲存你所建立的 Fabricjs JSON 格式的圖像。

也能夠用來後端產生圖像的簡易工具。

在 Fabricjs 不斷的更新後,2.0 版之後可以在 nodejs 環境下用和瀏覽器端一樣的語法。不需再另外用特別的方法才能創建 canvas,使用起來就更方便囉。

不過還是會有些和瀏覽器不同的地方和限制
且版本目前也還無法支援 node 10
相關限制請看 Fabric limitations in node.js

建構環境

需要使用到 Node.js 環境,若沒有的話必須若沒有的話必須先行安裝。建議使用 nvm 方便管理自己 node 版本 (自己很常遇到有的東西要新的版本有的東西只能支援舊的 QQ)

nodejs
nvm
注意!目前還不支援 node 10 環境,記得要安裝 node 8

使用 npm 安裝 fabric 套件

npm init
npm install fabric

fabric 套件相依了 node-canvas 不過我們只需要安裝 fabric 他就會自己安裝相依的套件不需要額外在自己安裝囉。

安裝完我們就可以開始寫 nodejs 程式了

先把需要用到的套件引進

注意到需要抓到引入套件底下的 require('fabric').fabric

const fs = require('fs'),
fabric = require('fabric').fabric

或是使用 ES6

import fs form 'fs'
import {fabric} from 'fabric'

不過 node 8 還沒有 es6 的 ES Module 功能,必須要在使用 babel 來編譯,這邊我就先不用那麼麻煩了。

這邊因為要把 Base64 編碼轉換成檔案,所以也需要載入內建的 fs module。

像瀏覽器端一樣使用 canvas

需注意的是這邊第一個參數不需要填入 id 所以第一個參數給 null 就可以了

const canvas = new fabric.Canvas(null, {
  width: 200,
  height: 200,
  backgroundColor: 'black'
})

const rect = new fabric.Rect({
  width: 100,
  height: 100,
  top: 0,
  left: 0,
  fill: 'red'
})

canvas.add(rect)

最後就是匯出成 Base64 後利用 fs 模組把 Base64 寫成檔案囉!

匯出並存擋

const base64Data = canvas.toDataURL().replace(/^data:image\/png;base64,/, '')

// save base64
fs.writeFile('./out.png', base64Data, 'base64', function (err) {
  console.log(err)
})

配合 http server

這邊稍為實作了一下簡易的 http server 讓使用者能夠自行上傳圖片後存檔到伺服器,後端透過 nodejs 操作 fabric 強迫加上浮水印後存擋。

server.js

主要處理兩個路由而已

主要畫面 '/' response: index.html

輸入 http://127.0.0.1:8887 後可得到下面 html 畫面

  if (params.path === '/') {
    fs.readFile('./public/index.html', (err, data) => {
      console.log('write index.html')
      res.writeHead(200, {'Content-Type': 'text/html'})
      res.write(data)
      res.end()
    })
  }

'/add' 只接受 POST { data: "json string" }

這邊和上面範例很像,讀取到 request 的 body 後,把 JSON 存擋讀出來,並且利用 new fabric.Text 加入浮水印,最後再做寫入。

  ...
  else if (params.path === '/add') {
    console.log('user upload file')
    let body = ''
    req.on('data', (data) => {
      body += data
    })
    req.on('end', () => {
      console.log('body end')
      const canvas = new fabric.Canvas(null, {
        width: 500,
        height: 500,
        backgroundColor: 'black'
      })
      const jsonSave = JSON.parse(body).data
      canvas.loadFromJSON(jsonSave, () => {canvas.renderAll()})
      canvas.add(new fabric.Text('Nono', {
        opacity: 0.5,
        fontSize: 60,
        fill: 'blue'
      })).renderAll()

      const base64Data = canvas.toDataURL().replace(/^data:image\/png;base64,/, '')
      const filename = `./output/fabric-${Date.now()}.png`
      // save base64
      fs.writeFileSync(filename, base64Data, 'base64', (err) => {
        console.log('err: ' + err)
      })
      const img = fs.readFileSync(filename)
      res.writeHead(200, {'Content-Type': 'image/png'})
      res.end(img, 'binary')
    })

完整 server.js 程式

const fabric = require('fabric').fabric
const fs = require('fs')
const http = require('http')
const url = require('url')
const PORT = 8887
const HOST_NAME = '127.0.0.1'
const server =  http.createServer((req, res) => {
  const params = url.parse(req.url, true)
  if (params.path === '/') {
    fs.readFile('./public/index.html', (err, data) => {
      console.log('write index.html')
      res.writeHead(200, {'Content-Type': 'text/html'})
      res.write(data)
      res.end()
    })
  } else if (params.path === '/add') {
    console.log('user upload file')
    let body = ''
    req.on('data', (data) => {
      body += data
    })
    req.on('end', () => {
      console.log('body end')
      const canvas = new fabric.Canvas(null, {
        width: 500,
        height: 500,
        backgroundColor: 'black'
      })
      const jsonSave = JSON.parse(body).data
      canvas.loadFromJSON(jsonSave, () => {canvas.renderAll()})
      canvas.add(new fabric.Text('Nono', {
        opacity: 0.5,
        fontSize: 60,
        fill: 'blue'
      })).renderAll()

      const base64Data = canvas.toDataURL().replace(/^data:image\/png;base64,/, '')
      const filename = `./output/fabric-${Date.now()}.png`
      // save base64
      fs.writeFileSync(filename, base64Data, 'base64', (err) => {
        console.log('err: ' + err)
      })
      const img = fs.readFileSync(filename)
      res.writeHead(200, {'Content-Type': 'image/png'})
      res.end(img, 'binary')
    })
    
  } else {
    res.write('404')
    res.end()
  }
  
})

server.listen(PORT, HOST_NAME, () => {
  console.log(`Server running at http://${HOST_NAME}:${PORT}/`);
})

client 端程式

這邊做的事情很簡單,使用者點選儲存到伺服器按鈕後,將 JSON 存擋用 http post 傳送到後端儲存。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <style>
    html, body {
      height: 100%;
      width: 100%;
    }
    canvas {
      border: 1px solid #000;
    }
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }
  </style>
  <title>Save fabric json to server</title>
</head>
<body>
  <canvas id="canvas"></canvas>
  <button id="save2Server">儲存到伺服器</button>
  <div id="imgSet">
    <img src="" alt="" id="serverImg">
  </div>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.4.3/fabric.js"></script>
  <script>
  const imgSet = document.getElementById('imgSet')
  const serverImg = document.getElementById('serverImg')
  const canvas = new fabric.Canvas('canvas', {
    width: 500,
    height: 500,
    backgroundColor: '#fff'
  })
    const rect = new fabric.Rect({
      height: 100,
      width: 100
    })
    canvas.add(rect)
    const save2Server = document.getElementById('save2Server')
    save2Server.addEventListener('click', () => {
      const payload = {
        data: JSON.stringify(canvas)
      }
      const host = '127.0.0.1:8887'
      fetch(`http://${host}/add`, {
        method: 'POST',
        body: JSON.stringify(payload),
        headers: new Headers({
          'Content-Type': 'application/json'
        })
      })
        .then(res => res.blob())
        .then(blob => {
          serverImg.src = URL.createObjectURL(blob)
        })
    })
  </script>
</body>
</html>

今日範例使用說明

clone 到本地

git clone https://github.com/nono1526/fabric-nodejs-upload

安裝套件

進入資料夾後下 npm install

執行 server

npm run serve

連線到用戶端

連線到 http://127.0.0.1:8887
可以看到畫面,點選儲存至伺服器

今日小結

其實之前工作上有遇到要為客戶的圖片加上浮水印的需求,不過那時候後端使用的是 PHP,所以浮水印當時是加在前端做,這樣做可能會有被拿掉的風險,若是能在伺服器端使用 fabricjs 來加浮水印就不會有被串改的問題了。

相關連結

Day - 11 把畫布序列化
今天的程式因為包含主機端放在所以放在 github
本日程式 - github


上一篇
Day 18 - Fabricjs 實作: 圖片上傳並透過拖曳進入 canvas
下一篇
Day 20 - Fabricjs 實作網格系統
系列文
Fabricjs 筆記30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言