TOP > カテゴリ > JavaScript >

Web Workerを使用してプログレスバーをマルチスレッドで動かす[HTML5]

HTML5で追加された<progress>タグのプログレスバーを使用してみると、プログレスバーが思ったように動作しない経験はありませんか?

プログレスバーはマルチスレッドのWeb Workerを使用しないと正常に動作しません。という事でプログレスバーを動作させる方法をご紹介します。

プログレスバーを実行してみる

ボタンを押すとWeb Workerを使用してプログレスバーを動作させます。

※ページの読み込みが完了してから実行してください。

事前準備

お使いのブラウザがChromeの方は「Web Workerに画像データを値渡しではなく参照渡しする」の記事の事前準備を行ってください。

※事前準備を行わないとサンプルが動作しませんのでご了承ください。

サンプルコード

サンプルはWeb WorkerとHTMLの2つのファイルとなります。

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    
    }
    postMessage({'type':1,'max':(height-1),'pos':y});
  }     
}
 
////////////////////////////////////////////////////////////////////////////////
// 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({'type'   : 2 ,
               '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;

// プログレスバー
var pgs;
  
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(){  
 
  // 処理速度の違いがわかるようにサイズをリサイズする 
  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);
  
  // IEのプログレスバーは1度使用したら汚染されるので、
  // 毎回、プログレスバーを生成する
  document.getElementById("pg_parent").innerHTML = '<progress id="MyProgress" style="width:300px;"></progress>';
  pgs = document.getElementById("MyProgress");  
  
  pgs.max = dmy_canvas.height-1;
  pgs.value = 0;
  
  effect_worker.postMessage({'src_width':source.width,'src_height':source.height,'src_raw': source.data},
                             [source.data.buffer]);                                            
}
 
// workerからのメッセージ
effect_worker.onmessage = function (event) {
  var type = event.data.type
  
  // ---------------------
  //  プログレスバー            
  // ---------------------     
  if (type == 1){    
      pgs.max   = event.data.max;
      pgs.value = event.data.pos;
      return;
      
  // ---------------------
  //  処理の完了            
  // ---------------------       
  }else if (type == 2){ 
    
    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;
  }
} 
 
// ユーザーによりファイルが追加された  
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>
<div id="pg_parent"></div>
<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> 

Web Workerの流れ

メインスレッドではボタンを押した時にeffect_worker.postMessage()でWeb Workerにメッセージを送信します。受信はeffect_worker.onmessage = function (event) {}で行います。

Web WorkerからはpostMessage()でメインスレッドにメッセージを送信します。

その他

大半のコードは標準的なcanvas系の操作ですのでMDNで確認して下さい。

Web Worker側のEffectNegative()はRGBの色を反転させる画像処理ですので、興味が無い方は覚える必要はないです。





関連記事



公開日:2016年05月18日 最終更新日:2017年05月14日
記事NO:01964