このページの本文へ

PROGRAMMING 古籏一浩のJavaScriptラボ第82回

iOS 5で使えるWeb Workersでカメラアプリ作ってみた

2012年02月22日 11時00分更新

古籏一浩

  • この記事をはてなブックマークに追加
本文印刷

メモリ不足で処理できない場合

 サンプル2は、画像サイズが小さい場合は問題ありませんが、画像の総ピクセル数が多い場合は処理が終わらず、場合によってはブラウザーがクラッシュしてしまうことがあります。

 iPhoneはPCと異なり、使用できるメモリが非常に少ないので、大量のピクセルを一括して処理させるのは無理があります。特に、iOS5ではCanvas内のピクセルデータを配列にコピーするため、余計にメモリを消費してしまいます。

 そこで、Canvas内の画像を横1ラインずつワーカーに渡して処理させるようように改良します。横のピクセル分×4要素しかメモリを消費しないので、ブラウザーがクラッシュする可能性は減ります。

 1ラインずつ処理するように改良したのがサンプル3です。実行すると上からゆっくりと画像がグレースケールになっていくのが分かります。

【図】fig8.png
処理する画像。ボタンをタップすると処理が開始される
【図】fig9.png
上から1ラインずつグレースケールに変換されていく
【図】fig10.png
ワーカーの処理が終了するとアラートダイアログが表示される
【図】fig11.png
Canvas内の画像がグレースケールに変換される

■サンプル3


<!DOCTYPE html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content= "initial-scale=1">
    <title>画像フィルタ</title>
  </head>
  <body>
    <h1>画像フィルタ</h1>
    <form>
      <input type="button" id="effect" value="画像フィルタ">
    </form>
    <canvas id="myCanvas" width="1000" height="1500" style="border:1px solid black"></canvas>
    <script>
      // Canvasに画像を描画
      var canvasObj = document.getElementById("myCanvas");
      var context = canvasObj.getContext("2d");
      var canvasW = canvasObj.width;
      var canvasH = canvasObj.height;
      var imgObj = new Image();
      imgObj.src = "photo.jpg"; // 画像のURLを指定
      imgObj.onload = function(){
        context.drawImage(imgObj,0,0, canvasObj.width, canvasObj.height);
      }
      // クリックしたらエフェクト処理を開始
      document.getElementById("effect").addEventListener("click", function(){
        var imageData = context.getImageData(0,0, canvasObj.width, canvasObj.height);
        // ワーカー生成&イベント設定
        var effectWorker = new Worker("effect.js");
        var counter = 0;  // 処理すべきY座標の値
        var pixelData = new Array();  // ワーカーで処理するピクセルを入れる配列
        var outputData = context.createImageData(canvasW, 1);
        effectWorker.addEventListener("message", function(event){
          // ワーカーから渡されたピクセルを配列にコピー
          for(var i=0; i<event.data.length; i++){
            outputData.data[i] = event.data[i];
          }
          context.putImageData(outputData, 0, counter);
          counter = counter + 1;
          if (counter < canvasH){
            // ピクセルデータを配列にコピーする
            var startPoint = (canvasW * 4) * counter; // ピクセルの処理を行う配列の位置を求める
            for(var i=0; i<canvasW*4; i++){
              pixelData[i] = imageData.data[startPoint + i];
            }
            // ワーカーのエフェクト処理を実行
            effectWorker.postMessage({
              pixels: pixelData,  // ピクセルデータ(配列)
              width: canvasW  // Canvasの横幅
            });
          }else{
            alert("完了しました");
          }
        }, true);
        // ピクセルデータを配列にコピーする
        var pixelData = new Array();
        for(var i=0; i<canvasW*4; i++){
          pixelData[i] = imageData.data[i];
        }
        // ワーカーのエフェクト処理を実行
        effectWorker.postMessage({
          pixels: pixelData,  // ピクセルデータ(配列)
          width: canvasW  // Canvasの横幅
        });
      }, false);
    </script>
  </body>
</html>

■JavaScript effect.js


addEventListener("message", function(event){
  var pixelData = event.data.pixels;
  for(var x=0; x<event.data.width; x++){
    var pointer = x * 4;  // RGBαで4配列
    var red = pixelData[pointer + 0];
    var green = pixelData[pointer + 1];
    var blue = pixelData[pointer + 2];
    var gray = Math.floor(green * 0.6 + red * 0.3 + blue * 0.1);
    pixelData[pointer + 0] = gray;
    pixelData[pointer + 1] = gray;
    pixelData[pointer + 2] = gray;
  }
  postMessage(pixelData);  // 終了したら呼び出し元にピクセルデータを返す
}, false);

 サンプル3は、1ラインずつ処理しているので、すべての処理が終わるまでに非常に時間がかかります。メモリ消費量との兼ね合いを見ながら、一度に処理するラインの単位を調整するとよいでしょう。

 なお、ブラウザーでは処理できてもPhoneGapではメモリ不足で処理できないことがあります。余裕を持ってプログラムを作成しておくのが安全です。

Web Professionalトップページバナー

この記事の編集者は以下の記事をオススメしています

ASCII.jp会員サービス 週刊Web Professional登録

Webディレクター江口明日香が行く