Fabric.js实作 Node.js 上传图片及操作 Fabricjs 为图片加上浮水印

2018-11-27 23:41:55
1368次阅读
0个评论
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 来加浮水印就不会有被串改的问题了。
收藏00

登录 后评论。没有帐号? 注册 一个。