Canvasを使ったグロー効果が幻想的!!HTML5/Videoタグ用jQuery Plugin

新年、明けましておめでとうございます。旧年中、このブログ読んで下さった方々、お世話になりました。今年もがんばりますので、よろしくお願いします
。
新年一発目のお題は、Philips 社 の TV に装備された Ambilight
を模した jQuery Plugin の紹介です。このギミック、日本ではほとんど馴染みがないと思いますが、液晶 TV の背面外周に取り付けられた LED が画面に合わせた色に発光し、映画館の環境 (アンビエント) 光の雰囲気を演出するというものです。

デモ & スクリプト
CodeZine さんの 「HTML5で実現できるマルチメディア系機能 – ここが違う!サンプルで見るHTML5(4)」 で紹介されていた デモ を辿って、セルゲイさんのエントリー 「Ambilight for the tag video」 に行き着きました。Firefox 3.5、Opera 10.5、Safari 4、Google Chrome 4 で動作するそうです。彼の作品を元に作った デモはコチラ です (映像を差し替えただけなんですが…)。
2011年1月16日追記
- 先日 iTunes をアップデートした時に、DivX を同時にインストールしたんですが、デモのグロー効果が表示されませんでした (アンインストールしたら直りました)。おそらく Canvas の色情報が取得出来なくなったと思われます。HTML5 に対応した DivX をインストールする場合は注意した方が良いと思います。
スクリプト ambilight.js には MIT ライセンス が課されています。jQuery プラグインという作りではなく、1箇所 $ の参照があるだけですので、何とかすれば単独で動作するようになるでしょう。使い方は、先のデモのソースをご覧ください。
以下、ロシア語の記事を参照しながら中身を紹介してみたいと思います。
1.アルゴリズム
本物の TV では、背面パネルに並べられた LED が異なる色に発光します。発光ダイオードの色は、ほぼ画面の位置に応じた色を反映して光ります。場面が変化するとそれに応じて LED の色も変化するシカケです。
同じ事をするために、現在のフレームを元に各 LED の色を決定し、光らせる方法を考える必要があります。さぁ、やってみましょう!
2.発光ダイオードの色を選択する
便宜上、LED が5個あるとしましょう。従って、まずビデオフレームを取り出し、LED の数だけエリアを分割し、各エリアの平均色を算出します。これがハイライトすべき色となります。

まず、現在のビデオフレームから色を取り出すため、一旦 drawImage メソッドで <canvas> に描画します。
var canvas = document.createElement('canvas'),
video = document.getElementsByTagName('Video')[0];
ctx = canvas.getContext('2d');
// Must expose the canvas size
canvas.width = video.width;
canvas.height = video.height;
// Draw frame
ctx.drawImage(video, 0, 0, video.width, video.height);
現在のフレームを得た後は、画像の1辺にどんな色のピクセルがあるかを知る必要があります。このために getImageData() メソッドを使います。
/* The width of the field, which we will analyze */ var block_width = 50; var pixels = ctx.getImageData(0, 0, block_width, canvas.height);
オブジェクト pixels は、全ピクセルの色情報が含まれたデータ・プロパティです。それらは少し特殊なフォーマット – 全ピクセルの RGBA コンポーネントの配列 – で構成されています。例えば、最初のピクセルの色と透明度アルファ値を知るためには、配列 data の最初の4つの要素を参照する必要があります。以下同様に、2番目のピクセルは、次の4つの配列に対応します。
var pixel1 = {
r: pixels.data[0];
g: pixels.data[1];
b: pixels.data[2];
a: pixels.data[3];
};
var pixel2 = {
r: pixels.data[4];
g: pixels.data[5];
b: pixels.data[6];
a: pixels.data[7];
};
次に、取得した全ピクセルを5つのグループ (最初に決めた LED の個数分) に分け、各グループの色を解析します。
function getMidColors() {
var width = canvas.width,
height = canvas.height,
lamps = 5, // number of LEDs
block_width = 50, // width of the analyzed region
block_height = Math.ceil(height / lamps), // height of the analyzed block
pxl = block_width * block_height * 4, // how many components of the RGBA - in one area
result = [],
img_data = ctx.getImageData(0, 0, block_width, h),
total = img_data.data.length;
for (var i = 0; i < lamps; i++) {
var from = i * width * block_width;
result.push(calcMidColor(img_data.data, i * pxl, Math.min((i + 1) * pxl, total_pixels - 1)));
}
return result;
}
この関数は単に、ブロック中を解析し、calcMidColor() 関数を使って色の平均値を算出しているだけです。ブロック中の輝度に基づいた色の平均値を算出するために、トリッキーな数式の類を適用する必要はありません。各色コンポーネントの算術平均を計算するだけで十分なのです。
calcMidColor() 関数は、次の通りです。
function calcMidColor(data, from, to) {
var result = [0, 0, 0];
var total_pixels = (to - from) / 4;
for (var i = from; i <= to; i += 4) {
result[0] + = data[i];
result[1] + = data[i + 1];
result[2] + = data[i + 2];
}
result[0] = Math.round(result[0] / total_pixels);
result[1] = Math.round(result[1] / total_pixels);
result[2] = Math.round(result[2] / total_pixels);
Return result;
}
これで LED の色を決めることが出来たわけですが、このままではちょっとインパクトがありません。LED は明らかに発光輝度のレベルに応じて光るため、彩度 (Saturation) と明度 (Brightness) を増やす必要があります。この計算を行うためには、HSV 色空間モデル – 色相、彩度、明度 – が非常に便利で、彩度、明度の値に適当な係数を掛け合わせるだけで十分です。それゆえ、RGB モデルで保持している色情報を HSV モデルに変換し、彩度と明度を増やし、再び RGB に変換しなおす処理を行います (RGB から HSV への変換 はネット上で簡単に見つかります)。
function adjustColor(color) {
color = rgb2hsv(color);
color[1] = Math.min(100, color[1] * 1.4); // Saturation
color[2] = Math.min(100, color[2] * 2.7); // brightness
return hsv2rgb(color);
}
3.グロー効果の描画
次は、円形に広がる LED 光源のグラデーションを表現する最適な方法についてです。的確な視覚効果を得るためには、非常に沢山の複雑な計算をしなければなりません。各 LED の位置と距離、それに光の減衰を考慮に入れ、隣接する色を混ぜ合わせるなどです。ここではもう少し楽をします。通常の線形なグラデーションを描画するだけで信じられない輝き感を演出する特殊なマスクを挿入することにします。
グラデーション自体は単純です。最初に createLinearGradient() 関数でグラデーションを作り、以降 addColorStop() で色を追加していき、最後にレンダリングします。
// To create a new glow on canvas
var light_canvas = document.createElement('canvas'),
light_ctx = light_canvas.getContext('2d');
light_canvas.width = 200;
light_canvas.height = 200;
var midcolors getMidColors = (), // averaged color
grd = ctx.createLinearGradient(0, 0, 0, canvas.height); // gradient
for (var i = 0, il = midcolors.length; i < il; i++) {
grd.addColorStop(i / il, 'RGB (' + adjustColor(midcolors[i]).join(',') + ')');
}
// Draw the gradient
light_ctx.fillStyle = grd;
light_ctx.fillRect(0, 0, light_canvas.width, light_canvas.height);

これにより、左の様なグラデーションを作ることが出来ます。
4.マスク
マスクは Photoshop で、スポットライト光源の効果を作り出す、すばらしいフィルタ 照明効果
を使って作ります。レイヤーを白で埋め、およそ次のような設定でフィルタを適用します。

作ったスポットライトは次の様になります。

レイヤーの描画モードを 「比較(明)」 に変え、複製し、スケールを変えたり、透明度を変えたりしながら、最終的には次のようにします。

この画像は、白を透明色に設定した白黒画像なので、マスクとするのは非常に簡単です。先に作り出したグラデーションの上にかぶせれば、素敵なグロー効果を得ることが出来ます。

最も重要なことは、見栄えと効果の強さをプログラミングをし直すことなく変えられるという事です。
左側のグロー効果が出来たので、右側についても同様にマスクを作成します。最後に、適当な周期でハイライトを更新するコントローラーを記述するだけです。これは長くて退屈な解説となるので、スクリプト を見てもらった方が簡単です!
§あとがき
今回、ロシアン・サイトの翻訳に初挑戦したわけですが、Chrome を使って英語に翻訳すれば結構イケルということが分かりました。以前から、Web 技術に関してはロシア人も活躍しているように感じていましたので (Steve Souders さんや Stoyan Stefanov さんのブログで議論していたり、参照されたりしています)、これからはちょっと幅を広げてみたいと思います
。
最近のコメント