Web Workerに画像データを値渡しではなく参照渡しする[JavaScriptのマルチスレッドの高速化]
HTML5のWeb Workerを使用するとシングルスレッドのJavaScriptをマルチスレッドのように重たい処理を並行して実行することが可能となります。ただし、Web Workerに渡すデータは通常は「値渡し」となり処理速度が遅いです。そこで、今回はデータを「参照渡し」(ポインタ)でWeb Workerに渡す方法をご紹介します。
事前準備
お使いのブラウザがChromeの場合はローカル環境でサンプルコードを実行するとセキュリティ保護の観念から「SecurityError」が発生します。但し、Webサーバーにアップロードして実行するとエラーは発生しません。
ローカル環境でこのエラーを回避するにはChromeを起動するショートカットファイルの設定を変更します。
ショートカットファイルを右クリックして「プロパティ」を開きます。
次に「ショートカット」タブの中にある「リンク先」の後方に
を追加してOKボタンを押します。
空白は「半角1つ」です。
そして、chromeを閉じてから設定したショートカットから再起動します。
これでローカル環境でもセキュリティエラーは発生しなくなります。
サンプルコード
サンプルはWeb WorkerとHTMLの2つのファイルとなります。
サンプルを実行する前に「F12キー」を押して「開発者ツール」を起動します。次に画像ファイルを読み込んで実行ボタンを押すだけです。
Web Worker側 - effect.js
// ネガティブ function EffectNegative(source){ var height = source.height; var width = source.width; var src = source.data; var sRow,sCol for (var y = 0; y < height; y++) { sRow = (y * width*4); for (var x = 0;x < width; x++) { sCol = sRow + (x * 4); src[sCol] = 255 - src[sCol]; src[sCol + 1] = 255 - src[sCol + 1]; src[sCol + 2] = 255 - src[sCol + 2]; src[sCol + 3] = 255; // Transparency } } } //////////////////////////////////////////////////////////////////////////////// // Worker イベント //////////////////////////////////////////////////////////////////////////////// onmessage = function (event) { // 擬似的にImageDataオブジェクトを生成する var source = {'data' : event.data.src_raw, 'width' : event.data.src_width, 'height': event.data.src_height}; // ネガティブ変換 EffectNegative(source); // 変換後のデータを送信する[参照渡し] postMessage({'width' : source.width, 'height' : source.height, 'raw' : source.data},[source.data.buffer]); };
HTML側 - index.html
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <script> // ワーカーの生成 var effect_worker = new Worker('effect.js'); // キャンバス var src_canvas; var src_ctx; var dst_canvas; var dst_ctx; var dmy_canvas; var dmy_ctx; // イメージ var image; window.onload = function(){ src_canvas = document.getElementById("SrcCanvas"); src_ctx = src_canvas.getContext("2d"); dst_canvas = document.getElementById("DstCanvas"); dst_ctx = dst_canvas.getContext("2d"); dmy_canvas = document.getElementById("DmyCanvas"); dmy_ctx = dmy_canvas.getContext("2d"); image = document.getElementById("img_source"); } function run(){ console.time('処理'); // 処理速度の違いがわかるようにサイズをリサイズする if (window.navigator.msSaveBlob) { // IE/Edge dmy_canvas.width = 1000; dmy_canvas.height = 1000; }else{ dmy_canvas.width = 4000; dmy_canvas.height = 4000; } dmy_ctx.drawImage(src_canvas,0,0,dmy_canvas.width,dmy_canvas.height); var source = dmy_ctx.getImageData(0, 0, dmy_canvas.width, dmy_canvas.height); // 値渡し //effect_worker.postMessage({'src_width':source.width,'src_height':source.height,'src_raw': source.data}); // 参照渡し effect_worker.postMessage({'src_width':source.width,'src_height':source.height,'src_raw': source.data}, [source.data.buffer]); // イメージデータのサイズが0の場合は「参照渡し」に成功している事となる console.log('source.data.length:'+source.data.length); } // workerからのメッセージ effect_worker.onmessage = function (event) { dst_canvas.width = event.data.width; dst_canvas.height = event.data.height; var imagedata = dst_ctx.createImageData(event.data.width, event.data.height); var src = event.data.raw; var dst = imagedata.data; for(var i = 0; i < src.length; i++){ dst[i] = src[i]; } dst_ctx.putImageData(imagedata,0,0); document.getElementById("msg_destination").innerHTML = '[変換後] ' + dst_canvas.width + ' x ' + dst_canvas.height; console.timeEnd('処理'); } // ユーザーによりファイルが追加された function onAddFile(event) { var files; var reader = new FileReader(); if(event.target.files){ files = event.target.files; }else{ files = event.dataTransfer.files; } // ファイルが読み込まれた reader.onload = function (event) { // イメージが読み込まれた image.onload = function (){ src_canvas.width = image.width; src_canvas.height = image.height; // キャンバスに画像を描画 src_ctx.drawImage(image,0,0); document.getElementById("msg_source").innerHTML = '[元の画像] ' + image.width + ' x ' + image.height; }; // イメージが読み込めない image.onerror = function (){ alert('このファイルは読み込めません。'); }; image.src = reader.result; }; if (files[0]){ reader.readAsDataURL(files[0]); } document.getElementById("inputfile").value = ''; } </script> </head> <body> <h4>画像の読み込み</h4> <p></p> <input type="file" id="inputfile" accept="image/jpeg,image/png,image/gif" onchange="onAddFile(event);"> <p></p> <button onclick="run();">実行</button> <p></p> <h3 id="msg_destination">[変換後]</h3> <p></p> <canvas id="DstCanvas"></canvas> <p></p> <h3 id="msg_source">[元の画像]</h3> <p></p> <img id="img_source" style="display:none;"> <canvas id="SrcCanvas"></canvas> <canvas id="DmyCanvas" style="display:none;"></canvas> <p></p> </body> </html>
実行後
「開発者ツール」のコンソール(Console)に処理時間とsource.data.lengthのサイズが表示されます。
HTML側の44行を実行させると「値渡し」で、47行目を実行させると「参照渡し」となります。
[値渡しの結果]
[参照渡しの結果]
参照渡しの場合はワーカーに渡したデータは再利用できなくなります。ですので、source.data.lengthが0となります。
このサンプルでの値渡しと参照渡しの速度差は微々たるものですが、ワーカーに渡すデータのサイズや、データ数が多ければ速度差に大きな違いがでてきます。
以上となります。