LoginSignup
9
1

More than 3 years have passed since last update.

前編の内容

前編では、Node-REDでWebAssemblyを使う方法として、functionノードにWebAssemblyバイナリを埋め込む方法と、独自ノード内でWebAssemblyを呼び出す方法の前準備までを説明しました。

後編では、前編で作成したWebAssemblyコンパイル済みのPNGトリミングルーチンを使って独自ノードを作っていきます。

(承前) 第二段階: PNG画像のトリミングをする独自ノードの作成

Node-REDノードの作成

Node-RED Nodeの作り方のドキュメントを参考にしながら、モジュールを作っていきます。

まずnpmモジュールの雛形を作ります。

% mkdir node-red-contrib-png-crop
% cd node-red-contrib-png-crop
% npm init -y
...

%

次に、package.jsonを更新します。

package.json

  "name": "node-red-contrib-png-crop",
  "version": "1.0.0",
  "description": "Cropping PNG image",
  "keywords": [],
  "author": "",
  "license": "ISC",
  "node-red": {
    "nodes": {
      "png-crop": "png-crop.js"
    }
  },
  "dependencies": {
  }
}

ランタイム側のロジックはpng-crop.jsとして記述します。

png-crop.js
const pngcropwasm = require('./pngcrop');

module.exports =  (RED) => {
    function PngCropNode(config) {
        RED.nodes.createNode(this,config);
        const node = this;
        node.on('input', (msg, send, done) => {
            if (Buffer.isBuffer(msg.payload.img)) {
                const x = msg.payload.x || 0;
                const y = msg.payload.y || 0;
                const width = msg.payload.width || 100;
                const height = msg.payload.height || 100;
                const cropped_img = pngcropwasm.croppng(msg.payload.img, x, y, width, height);
                msg.payload.img = cropped_img;
                send(msg);
                done();
            } else {
                done('no image');
            }
        });
    };
    RED.nodes.registerType('png-crop', PngCropNode);
}

冒頭のrequire()でWebAssemblyバイナリにコンパイルされたPNGトリミングライブラリをモジュールとして読み込んでいます。そして、Node-REDのメッセージハンドラで、ペイロードに含まれているイメージファイルを引数としてトリミング関数を呼び出し、結果をメッセージとして送信しています。

あとは、エディタ側の記述です。とくに設定インタフェースはないので、最低限の記述のみしてあります。

png-crop.html
<script type="text/javascript">
    RED.nodes.registerType('png-crop', {
        category: 'function',
        color: '#F3B567',
        defaults: {
            name: {value: ""}
        },
        inputs: 1,
        outputs: 1,
        icon: "font-awesome/fa-crop",
        label: function() { return this.name ||"png-crop";}
    });
</script>

<script type="text/html" data-template-name="png-crop">
    <div class="form-row">
        <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
        <input type="text" id="node-input-name" placeholder="Name">
    </div>
</script>

<script type="text/html" data-help-name="png-crop">
    <p>A simple node that crops the image</p>
</script>

独自ノードのインストールとフローの作成

これをインストールして、フローを作ってみましょう。

% cd ~/.node-red/
% npm install ....../node-red-contrib-png-crop
...
% cd ...../node-red
% npm start

functionカテゴリに"png crop"というノードができています。
png cropノード

試しにWeb上のPNG画像をトリミングしてダッシュボードに表示するフローを作ってみます。

Web上のファイルのトリミング

[{"id":"924181c4.4467c","type":"tab","label":"Flow 1","disabled":false,"info":""},{"id":"fc70fd9.aa3a5","type":"inject","z":"924181c4.4467c","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":160,"y":100,"wires":[["aef153f1.97613"]]},{"id":"aef153f1.97613","type":"http request","z":"924181c4.4467c","name":"Ferris the crab","method":"GET","ret":"bin","paytoqs":"ignore","url":"https://rustacean.net/assets/rustacean-flat-happy.png","tls":"","persist":false,"proxy":"","authType":"","x":340,"y":100,"wires":[["b33daa13.f05df8"]]},{"id":"b33daa13.f05df8","type":"change","z":"924181c4.4467c","name":"","rules":[{"t":"move","p":"payload","pt":"msg","to":"payload.img","tot":"msg"},{"t":"set","p":"payload.x","pt":"msg","to":"480","tot":"num"},{"t":"set","p":"payload.y","pt":"msg","to":"350","tot":"str"},{"t":"set","p":"payload.height","pt":"msg","to":"300","tot":"str"},{"t":"set","p":"payload.width","pt":"msg","to":"300","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":540,"y":100,"wires":[["1f7259b8.b4e1e6"]]},{"id":"7497a53c.595bdc","type":"function","z":"924181c4.4467c","name":"Base64 encode","func":"const cropped = Buffer.from(msg.payload.img);\nmsg.payload = cropped.toString('base64');\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":340,"y":180,"wires":[["6dd7310.e04afd"]]},{"id":"6dd7310.e04afd","type":"ui_template","z":"924181c4.4467c","group":"c744f26f.11e5f","name":"","order":0,"width":0,"height":0,"format":"<div style=\"height: 300px; width: 300px\">\n<img src=\"data:image/png;base64,{{msg.payload}}\"\n alt='cropped image'\n />\n </div>","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":true,"templateScope":"local","x":520,"y":180,"wires":[[]]},{"id":"1f7259b8.b4e1e6","type":"png-crop","z":"924181c4.4467c","name":"","x":160,"y":180,"wires":[["7497a53c.595bdc"]]},{"id":"c744f26f.11e5f","type":"ui_group","z":"","name":"Default","tab":"22fdfd7c.238b92","order":1,"disp":true,"width":"6","collapse":false},{"id":"22fdfd7c.238b92","type":"ui_tab","z":"","name":"Home","icon":"dashboard","disabled":false,"hidden":false}]

RustマスコットのFerris the Crabの画像を持ってきて、顔の部分を切り出してui_templateダッシュボードに表示するフローになっています。

結果のダッシュボードがこちらです。
ダッシュボード

速度は?

気になるのが変換速度になります。
5K画像を100回トリミングする簡単なスクリプトを書いて、速度を調べました。

pure JS版(png-crop, pngjs利用)
const pngcrop = require('png-crop');

for (let i=0; i<100; i++) {
    pngcrop.crop('./test.png', './testout.png',
        {width: 1000, height: 1000, top: 10, left: 10},
        (err) => { if (err) throw err; });
}
Rust+WebAssembly+JS版
const pngcrop = require('./pngcrop');
const fs = require('fs');

for (let i = 0; i < 100; i++) {
    const input = fs.readFileSync('./test.png', {flag:'r'});
    const cropped = pngcrop.croppng(input, 10,10,1000,1000);
    fs.writeFileSync('./testout.png',cropped);
}

結果は下記の通りです。プログラムの作りが違うので直接は比べられませんが、WebAssemblyベースのほうが約1.4倍高速という結果になっています。もちろん、ネイティブコードで実装すればさらに高速化できますが、ここではNode.jsで完結するところに利点を見出しています。

所要時間 pure JS比
pure JS版 98秒 1
Rust+Wasm+JS版 68秒 0.69

今回はwasm-bindgenで生成したファイルを手でコピーしてnpmモジュールに追加していますが、wasm-packを使うとnpmモジュールの作成まで自動的に行えます。Node-REDのノードモジュールとして使うときには、wasm-bindgenの生成物をそのままコピーで十分かと思います。

最後に

やや無理やりな使い方ではありますが、RustとWebAssemblyの組み合わせでNode-REDノードが記述できることを示しました。

WebAssemblyによって、多様な言語で記述したプログラムがブラウザ上やNode.js上で安全に実行できるようになりました。また、WebAssembly System Interface(WASI)によってWebAssemblyはサンドボックスを備えた軽量な実行環境としても使われるようになってきています。今後は、エッジコンピューティングなどより広い応用範囲で使われる技術になっていくと期待しています。

参考文献

9
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
1