読者です 読者をやめる 読者になる 読者になる

hakobera's blog

技術メモ。たまに雑談

Canvas + File API + Drag&Drop API で Instagram みたいな画像フィルターを作ってみた

Instagram の画像フィルターみたいことを HTML5 (Canvas + File API + Drag&Drop API) で実装してみました。

Instagram みたいなの - jsdo.it - Share JavaScript, HTML5 and CSS

動作するブラウザは FireFox 3.6、Chrome 8 開発版です。(Chrome 7 は File API はサポートしていますが、どうやら iframe 内では動作しないため、上記 jsdo.it 上では動作しません。)

できること

これを
f:id:scalar:20101026123647p:image

こんな風に加工できます。
f:id:scalar:20101026123646p:image

大学時代に画像処理の研究をやっていた身としては、ブラウザだけでもここまで出来るようになったのか、と少し感慨深いです。

やっていること

  1. Drag&Drop API で画像ファイルを取得
  2. File API で画像ファイルを DATA URI 形式に変換して、Image オブジェクトで読み込み
  3. Image オブジェクトを Canvas の drawImage で描画
  4. Canvas の getImageData を利用して、画像のピクセルデータを取り出し
  5. 画像フィルターでピクセルデータの加工
  6. 加工したデータを Canvas の putImageData を利用して描画

解説

Drag & Drop

dragover, drop イベントに対してイベントリスナーを設定します。
ファイルデータは drop イベントの event.dataTranfer.files 配列から取り出します。1つしかファイルをドロップしなくても、配列で戻ってくる点だけ注意。

  window.addEventListener('dragover', function(event) {
      event.preventDefault(); // ブラウザのデフォルトの画像表示処理をOFF
  }, false);
  
  window.addEventListener('drop', function(event) {
    event.preventDefault();  // ブラウザのデフォルトの画像表示処理をOFF
    // 先頭のファイルを取り出します。
    var file = event.dataTransfer.files[0];
    // ファイルタイプ(MIME)で対応しているファイルか判定
    var fileType = file.type;
    if (!fileType.match(/image\/\w+/)){
      alert('画像ファイル以外は利用できません');
      return;
    }
  }

File API を利用して、DATA URL 形式に変換する

個々のファイルは FileReader オブジェクトを利用して読み込みます。readAsDataURL メソッドを利用して読み込むことで、ファイルを DATA URL 形式に変換することをができます。変換結果は、FileReader の onload メソッド中で result プロパティから取得することができます。

    var reader = new FileReader(); 
    reader.onload = function() {
      currentImage = new Image();
      currentImage.onload = function() {
        var normal = document.getElementById('normal');
        normal.click();
        drawImage(this, imageWidth, imageHeight);
      };
      currentImage.src = reader.result; // 画像を DATA URL 経由で表示
    };
    reader.readAsDataURL(file);
  }, false);

Canvas でピクセルデータを取得、変換、設定

getImageData でピクセルデータを管理する ImageData オブジェクトが取得できます。各ピクセルのデータ配列は、ImageData の data プロパティから取得します。RGBA の4バイトの配列から構成されるので、配列長は width * height * 4 になります。
createImageData で画像処理したデータの出力先 ImageData を作成し、画像フィルターで ImageData の data プロパティにデータを書き込みます。
画像処理した ImageData を、putImageData で Canvas に書き戻すことで、画像処理結果を表示することができます。

    var canvas = document.getElementById('canvas');
    var context = canvas.getContext('2d');
    var cw = canvas.width;
    var ch = canvas.height;
    
  // Canvas から ImageData オブジェクトの取得
    var inputData = context.getImageData(0, 0, cw, ch);

    // 出力バッファの作成
    var outputData = context.createImageData(cw, ch);
    
    // ピクセルデータは ImageData の data プロパティから取得
    var src = inputData.data;
    var dest = outputData.data;
    
    // フィルターによる画像処理
    filter(src, dest, cw, ch);
    
    // 加工データを Canvas に書き出し
    context.putImageData(outputData, 0, 0);
};

画像フィルターの例(反転フィルター)

var invertFilter = function(src, dest, width, height) {
  var size = width * height * 4;
  for (var i=0; i<size; i+=4) {
    dest[i+0] = 255 - src[i+0];
    dest[i+1] = 255 - src[i+1];
    dest[i+2] = 255 - src[i+2];
    dest[i+3] = src[i+3];
  }
};