メモリ不足で処理できない場合
サンプル2は、画像サイズが小さい場合は問題ありませんが、画像の総ピクセル数が多い場合は処理が終わらず、場合によってはブラウザーがクラッシュしてしまうことがあります。
iPhoneはPCと異なり、使用できるメモリが非常に少ないので、大量のピクセルを一括して処理させるのは無理があります。特に、iOS5ではCanvas内のピクセルデータを配列にコピーするため、余計にメモリを消費してしまいます。
そこで、Canvas内の画像を横1ラインずつワーカーに渡して処理させるようように改良します。横のピクセル分×4要素しかメモリを消費しないので、ブラウザーがクラッシュする可能性は減ります。
1ラインずつ処理するように改良したのがサンプル3です。実行すると上からゆっくりと画像がグレースケールになっていくのが分かります。
■サンプル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ではメモリ不足で処理できないことがあります。余裕を持ってプログラムを作成しておくのが安全です。