Quantcast
Channel: Node.jsタグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 9082

WebAssemblyをNode-REDで使う (後編)

$
0
0

前編の内容

前編では、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
constpngcropwasm=require('./pngcrop');module.exports=(RED)=>{functionPngCropNode(config){RED.nodes.createNode(this,config);constnode=this;node.on('input',(msg,send,done)=>{if(Buffer.isBuffer(msg.payload.img)){constx=msg.payload.x||0;consty=msg.payload.y||0;constwidth=msg.payload.width||100;constheight=msg.payload.height||100;constcropped_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(){returnthis.name||"png-crop";}});</script><script type="text/html"data-template-name="png-crop"><divclass="form-row"><labelfor="node-input-name"><iclass="fa fa-tag"></i> Name</label><inputtype="text"id="node-input-name"placeholder="Name"></div>
</script><script type="text/html"data-help-name="png-crop"><p>Asimplenodethatcropstheimage</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利用)
constpngcrop=require('png-crop');for(leti=0;i<100;i++){pngcrop.crop('./test.png','./testout.png',{width:1000,height:1000,top:10,left:10},(err)=>{if(err)throwerr;});}
Rust+WebAssembly+JS版
constpngcrop=require('./pngcrop');constfs=require('fs');for(leti=0;i<100;i++){constinput=fs.readFileSync('./test.png',{flag:'r'});constcropped=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はサンドボックスを備えた軽量な実行環境としても使われるようになってきています。今後は、エッジコンピューティングなどより広い応用範囲で使われる技術になっていくと期待しています。

参考文献


Viewing all articles
Browse latest Browse all 9082

Latest Images

Trending Articles