HTML5 + JavaScriptで画像処理 (エッジ検出)

古籏一浩のJavaScriptラボ ― 第14回 HTML5のcanvasで作る画像フィルター
を読んで、Canvasで画像の輝度値を編集できることを知ったので、ためしに実装してみることに。
GUIや画像の端の処理などかなり手抜きだが、FirefoxGoogle ChromeOperaで動作することを確認した。
処理はかなり重く、私の非力なマシンは実行するたび悲痛なファンの音を上げるほどである。。。でもしょうがないよね(´・ ω・`)
ちなみに3種類のブラウザで最も実行速度が早かったブラウザはOperaであり、もっとも遅いブラウザはFireFoxだった。

HTML5 + JavaScriptによるエッジ検出
http://jstap.web.fc2.com/test/html5/filtering/filtering.html

実行結果

ソースコード
2011/04/24追記
以下は古いソースコードです。最新は https://github.com/kojo-sugita/Image-filtering をご覧ください。

HTML

<p><b>オリジナル画像</b></p>
<img src="./lena.png"></img>
<br>
<input type="button" name="edge" value="エッジ検出" onClick="EdgeDetector()">
<br>
<p><b>エッジ画像</b></p>
<img src="./lena.png" style="display: none">
<canvas id="myCanvas" width="256" height="256">canvasに対応したブラウザーで実行してください</canvas>

JavaScript

/**
 * エッジ検出
 */
function EdgeDetector() {
  var _canvasW = 256; // 横幅256ピクセル
  var _canvasH = 256; // 縦幅256ピクセル
  var canvas = document.getElementById("myCanvas");
  var context = canvas.getContext("2d");
  var imgObj = new Image(_canvasW, _canvasH);
  imgObj.src = "http://jstap.web.fc2.com/test/html5/edge/lena.png";
  context.drawImage(imgObj, 0, 0);

  var grayImage = new Array(_canvasW * _canvasH);

  for(var y = 0; y < _canvasH; y++){
    for(var x = 0; x < _canvasW; x++){
      var pixelData = getPixel(canvas, x, y, _canvasW, _canvasH); // ピクセル値を取得する
      var R = pixelData.R;
      var G = pixelData.G;
      var B = pixelData.B;

      // グレースケール化
      grayImage[y * _canvasW + x] = ToGrayscale(R, G, B);
    }
  }

  /* Sobelフィルタ */
  var filter = new Array();
  filter[0] = 1; filter[1] = 0; filter[2] = -1;
  filter[3] = 1; filter[4] = 0; filter[5] = -1;
  filter[6] = 1; filter[7] = 0; filter[8] = -1;

  /* 空間フィルタリング */
  var resultImage = SpatialFiltering(grayImage, _canvasH, _canvasW, filter, 3);

  /* セット */
  for (var y = 0; y < _canvasH; y++) {
    for (var x = 0; x < _canvasW; x++) {
      var I = resultImage[y * _canvasW + x];
      setPixel(canvas, x, y, I, I, I, 255, _canvasW, _canvasH);
    }
  }
}

/**
 * 空間フィルタリング
 */
function SpatialFiltering(grayImage, height, width, filter, size_f) {

  var init = Math.floor(size_f / 2);
  var from = - init;
  var to = init;
  
  var resultImage = new Array(height * width);

  for (var k = 0; k < resultImage.length; k++) {
    resultImage[k] = 0;
  }

  for  (var i = init; i < height - init; i++) {
    for (var j = init; j < width - init; j++) {
      var sum = 0.0;
      /* フィルタリング */
      for (var n = from; n <= to; n++) {
        for (var m = from; m <= to; m++) {
          sum += grayImage[(i + n) * width + j + m] * 
            filter[(n + init) * size_f + m + init];
        }
      }
      resultImage[i * width + j] = Math.floor(Math.abs(sum));
    }
  }
  return resultImage;
}

/**
 * RBGをグレースケールにして返する
 */
function ToGrayscale(R, G, B) {
  R = Math.floor(R * 0.299);
  G = Math.floor(G * 0.587);
  B = Math.floor(B * 0.114);
  return R + G + B;
}

// GetPixel
// 戻り値はオブジェクトのプロパティでR,G,B
function getPixel(srcCanvas, x, y, canvasW, canvasH){
  if (window.opera) {
    var gContext = srcCanvas.getContext("opera-2dgame");
    var rgbStr = gContext.getPixel(x, y); // ピクセル値を取得する
    var R = eval("0x"+rgbStr.substring(1,3));
    var G = eval("0x"+rgbStr.substring(3,5));
    var B = eval("0x"+rgbStr.substring(5,7));
    return {R:R, G:G, B:B};
  }

  var imagePixelData = srcCanvas.getContext("2d").getImageData(x, y, 1, 1).data;
  var R = imagePixelData[0];
  var G = imagePixelData[1];
  var B = imagePixelData[2];
  return {R:R, G:G, B:B};
}

// SetPixel
function setPixel(srcCanvas, x, y, R, G, B, A, canvasW, canvasH){
  if (window.opera) {
    var gContext = srcCanvas.getContext("opera-2dgame");
    var rgbaColor = "rgba("+R+","+G+","+B+","+A+")";
    gContext.setPixel(x,y, rgbaColor);
    return;
  }
  var context = srcCanvas.getContext("2d");
  var pixelImage = context.createImageData(1, 1);
  pixelImage.data[0] = R;
  pixelImage.data[1] = G;
  pixelImage.data[2] = B;
  pixelImage.data[3] = A;
  context.putImageData(pixelImage, x, y);
}