position: absolute;

選択されているタグ : canvas

canvasでの画像加工 最初の一歩

GraphicalWeb (CSS, SVG, WebGL etc) Advent Calendar 2012

adventar

GraphicalWeb Advent Calendar 2012 の 5日目の記事です。

(おそらく)はじめまして、よろしくお願いします。GraphicalWeb Advent Calendar 2012のエントリーとしてcanvasについて書きます。

canvasについては改めて説明はしませんが、これからcanvasについて知ろうという方はHTML5.jpの記事が参考になると思います。

というわけでcanvasで行う画像の加工について書きます。元々描画機能もありますし、画像の重ねあわせというのも可能ですが、今回はcanvas内の画像の各ピクセルの情報取得とその編集/変更についてになります。

※今回のデモなどは一応HTML5が動作するブラウザを想定して作成していますが、あらゆる環境での動作を保証するものではありません。ご理解の程おねがいします。

基本的な考え方

JavaScriptでcanvasを操作するときには

var canvas = document.getElementById('canvasId');
var context = canvas.getContext('2d');

こういった形でコンテキストを取得しますが、このコンテキストからgetImageDataというメソッドを利用することでImageDataオブジェクトを取得できます。

var imageData = context.getImageData([開始X],[開始Y],[終了X],[終了Y]);

そしてこのImageDataオブジェクトのdataというプロパティで画像の各ピクセルの情報を取得/編集することができます。

imageData[i] = [0 - 255];

こうしてピクセルデーターを編集したImageDataオブジェクトをコンテキストに対してputImageDataというメソッドで反映させると対象のcanvasに反映されるという形になります。

context.putImageData(imageData, [開始X], [開始Y]); //一部省略

(参考:HTML5.jp「createImageData, getImageData, putImageData メソッド」

サンプル

簡単なサンプルを用意しました。「Result」の下に並んでいる数値の羅列が上部に表示されている20×2ピクセルの小さい赤の画像になります。

元々は一次配列でどこからどこまでが1つのピクセルを指すか見えにくいので、1ピクセルごとにデーターを区切って表示しています。

1ピクセル当たり4つの数値で構成されていてそれぞれ[R,G,B,A]の数値です。左上からX方向に進み、最小値は0、最大値は255。Aはアルファ(透明度)です。

今回は赤単色で透明度によるグラデーションの画像なので4つめの値が徐々に増えているかたちになります。

色の変更

上記のサンプルページの「change」というボタンを押すと下に青色の画像が表示されると思います。これは元の赤色のRGBAの値のRとBを入れ替えることで作っている画像です。それぞれのピクセルに対して行うことで画像の変換が行えるという例になります。

ちっちゃくてすいません。あんまり画像が大きいとデーターの表示がエライことになってしまうので…

画像処理のポピュラーなやり方

じつは上記のサンプルのJavaScriptは表示用の配列を作る関係で突飛な実装をしています。(興味がありましたら見ていただいてもいいですが…)というわけで比較的ポピュラーなやり方じゃないかな?というのを以下に書いておきます。

var imageData = context.getImageData(0,0,canvas.width,canvas.height);
for (var i = 0,len = imageData.data.length; i < len;i += 4){
    var r = imageData.data[i];
    var g = imageData.data[i + 1];
    var b = imageData.data[i + 2];
    var a = imageData.data[i + 4];
    //
    // データーを操作…
    //
    imageData.data[i] = r;
    imageData.data[i + 1] = g;
    imageData.data[i + 2] = b;
    imageData.data[i + 4] = a;
}
context.putImageData(imageData,0,0);

という形ですね。for文で回してputImageDataで反映させるという形です。

配列作ってまるっと反映させられないかなあと思いつく方法をいくらか試したんですが、今のところうまく行ってないです(誰かご存知ですか?)

ちょっと実用に近いサンプル

ちょっとiOS6から使えるようになったFileReaderも絡めたデモを作成してみました。画像データーをFileフィールドに指定すると写真を表示して明るさ調整やモノクロ化などを行うようになっています。

該当部分(forで回している中身)はこんな感じです

//RGBの取得
var r = pic.data[i],g = pic.data[i + 1],b = pic.data[i + 2];
//ボタンに応じてRGB値の編集
switch ($(this).attr('id')){
    case 'bright':
        r += 100;g += 100;b += 100;
        if (r > 255) r = 255;
        if (g > 255) g = 255;
        if (b > 255) b = 255;
        break;
    case 'dark':
        r -= 80;g -= 80;b -= 80;
        if (r < 0) r = 0;
        if (g < 0) g = 0;
        if (b < 0) b = 0;
        break;
    case 'gray':
        var gray = parseInt((r + g + b) / 3);
        r = gray;
        g = gray;
        b = gray;
        break;
}
//画像データーへの反映
pic.data[i] = r;
pic.data[i + 1] = g;
pic.data[i + 2] = b;

現状写真が大きいと重いとか(とくにiOS)方向がうまく取れなかったりとか(とくにiOS)あるのでまだ実用レベルで使えるか微妙ですが、「こういうのもあるよ」的に思っていただけたら幸いです。

※ iOS6でFileReaderからcanvasに入れるとサイズが大きすぎて一発では取り込めない(なにそれ?)のでMega pixel image rendering library for iOS6 Safariというライブラリを利用しています。

余談ですが、この機能を利用して漫画カメラみたいなものを作ったことがあります。さわりだけですがもし興味がありましたらどうぞ。「JavaScriptで『漫画カメラ』的画像加工

明日のGraphicalWeb Advent Calendar 2012は言わずと知れたAkihiro Oyamadaさんです。楽しみです。