読者です 読者をやめる 読者になる 読者になる

Make Local Happiness

自分の幸せは自分で作る!!!

AWSのLambda上でSVGをPNG画像に変換する〜Fabric編〜

Serverless SVG AWS Fabric API Gateway

f:id:iwate_takayu:20161003000656p:plain

最近いくかの案件でSVGを画像に変換したいことがありました。
d3.jsでグラフを表示した後に、その状態の画像をパワーポイントに貼り付けたかったり、 メールなどでレポートしたいことがあると思います、 そんな場合に、サーバでSVGを画像に変換できるととても便利です。

しかも、EC2ではなく、スポットで動くLambdaでできるとサーバ代も安く抑えられていいかと思います。

今回はAPISVGのデータを送って、 SVGCanvasに変換しPNGに書き出すということをやっていきます。

SVGCanvasPNG

SVGPNG画像に変換する方法

方法としては、Fabric.js(Canvas)を使う方法と、 Phantomjs、Graphicsmagickなど幾つかやり方があります。

Fabric.jsとは?

Canvasを簡単に活用できるためのフレームワークです。ただのCanvasモジュールでも同じことができるのですが、 今回はこちらの方が簡単そうでした。

Fabric.js is a framework that makes it easy to work with HTML5 canvas element. It is an interactive object model on top of canvas element. It is also an SVG-to-canvas parser.

実装してみる

github.com

コードはこちらから見ることができます。 実装の流れとしては、以下のようになります。

1, SVGエンコードしたものを、 一旦JsdomでHTMLに変換します。

2, FabricでCanvasオブジェクトを作成

3, SVGCanvasに追加し、バイナリーに変換

4, バイナリーをPNG形式でS3にアップロード

# hander.js

'use strict';

const aws = require('aws-sdk'),
  s3 = new aws.S3({ apiVersion: '2006-03-01' }),
  fabric = require('fabric').fabric,
  jsdom = require("jsdom"),
  md5 = require('md5'),
  env = require('./env.js');

module.exports.convert = (event, context, cb) => {
  const params = event.body;
  const svgString = jsdom.jsdom(decodeURIComponent(params.svgString)).body.innerHTML;
  const width = params.width;
  const height = params.height;

  const canvas = fabric.createCanvasForNode(width, height);

  fabric.loadSVGFromString(svgString , (objects, options) => {
    options.top = 0;
    options.left = 0;
    const svgGroups = fabric.util.groupSVGElements(objects, options);
    canvas.add(svgGroups).renderAll();
    const binaryData = canvas.nodeCanvas.toBuffer();
    const hash = md5(new Date().getTime());
    const imageKey = `${hash}.png`;

    s3.putObject(
      {
        'Bucket': env.BUKKET,
        'ACL': 'public-read',
        'Key': imageKey,
        'ContentType': 'image/png',
        'Body': binaryData
      },
      function (error) {
        if(error === null) {
          return cb(null,{
            status: 'success',
            msg: `https://s3-${env.REGION}.amazonaws.com/${env.BUKKET}/${imageKey}`,
          });
        } else {
          return cb(null, {
            status: 'error',
            msg: error
          });
        }
      }
    );
  });
};

実行してみる

簡単にSVGPNGに変換することができます。

$ serverless invoke -f lambdaSvgToPngFabric -p event.json
{
    "status": "success",
    "msg": "https://s3-ap-northeast-1.amazonaws.com/xxxxx/13808df0bd74460d995ce033620385dd.png"
}

Canvasのモジュールはローカルで動いても、Lambda上では動かない

実装中にハマったポイントとしては、Canvasのモジュールのビルドです。
開発環境はMacなのですが、 MacでビルドしたCanvasのモジュールは、Lambda上では動きません。 以下のようなエラーがでます。

{
    "errorMessage": "/var/task/node_modules/contextify/build/Release/contextify.node: invalid ELF header",
    "errorType": "Error",
    "stackTrace": [
        "Object.Module._extensions..node (module.js:434:18)",
        "Module.load (module.js:343:32)",
        "Function.Module._load (module.js:300:12)",
        "Module.require (module.js:353:17)",
        "require (internal/module.js:12:17)",
        "bindings (/var/task/node_modules/bindings/bindings.js:76:44)",
        "Object.<anonymous> (/var/task/node_modules/contextify/lib/contextify.js:1:96)",
        "Module._compile (module.js:409:26)",
        "Object.Module._extensions..js (module.js:416:10)"
    ]
}

なので、一旦以下のサイトの手順で、 node_modulesに必要なlibと、Lambda用にビルドする必要があります。 github.com

まとめ

canvasのモジュールをビルドするところ意外は特に問題なくできました。

ただ、この記事では触れていませんが、 SVG内でDivタグを描画する際に使うforeignObjectが描画できませんでした。。 なので、次はPhantomjsで試してみようと思います。