JavaScriptによる色空間

ペイントでパレットにない色を作成するとき開く画面がありますよね。カラフルなあれです。カラーピッカーって言うらしいですが...、あれをJavaScriptを使って再現しようと思います。使う知識はHSVとRGBについての知識で、RGBは文字通りRed、Blue、Greenですが、HSVとは何でしょう?僕も初めて知ったのですが、Hue、Saturation、Valueの頭文字で、それぞれ色相、彩度、明度のことらしいです。ペイントで使っているのはHSVとはちょっとばかし違うようですが、まぁ細かいことは気にせずやっていきたいと思います。さてさて問題なのはペイントではHSVで色を表していますが、HTMLではRGB(16進数で例えば#FF0000は赤)でしか表せない点ですね。よってHSVからRGBへと変換しなければなりません。そこでまずはHSVについてお勉強しましょう。
錯体のHSV色空間
図1 錯体のHSV色空間
Wikipedia: HSV色空間より抜粋)


HSVはそれぞれ以下のように定義します。
H: 0~360
S: 0.0~1.0
V: 0.0~1.0
Hについては図を見ての通りぐるっと一周360度です。その他は0~1の範囲にあるパラメータとします。さて、しかしながらこのHが曲者でして...、Hは色相ですのでペイントでやってみると、たとえば左上の点から上に沿って右に進ませていくっていう操作に当たります。このときRGBをじっくり観察してみると、
R ⇒ Y ⇒ G ⇒ C ⇒ B ⇒ M
(Y:Yellow、C:Cyan、M:Magenta)と移り変わります。要は、まずRを255に保ったままGが0から255へ変化、後Gを255に保ったままRが255から0へ変化...(以下略)と動きます。プログラムし辛い...、ヤダヤダ。と思っているところにWikipedia様が登場です。


H_i = \lfloor { H \over 60 } \rfloor mod 6

f = { H \over 60 } - H_i

p = V ( 1 - S ) \,

q = V ( 1 - f S ) \,

t = V ( 1 -  ( 1 - f ) S ) \,

\mbox{if } H_i = 0 \rightarrow R = V, G = t, B = p \,

\mbox{if } H_i = 1 \rightarrow R = q, G = V, B = p \,

\mbox{if } H_i = 2 \rightarrow R = p, G = V, B = t \,

\mbox{if } H_i = 3 \rightarrow R = p, G = q, B = V \,

\mbox{if } H_i = 4 \rightarrow R = t, G = p, B = V \,

\mbox{if } H_i = 5 \rightarrow R = V, G = p, B = q \,

Wikipedia: HSV色空間より抜粋)


なんと!こんな便利な変換式があるんですね。この計算のミソは、はじめにHiを60で割ってあげるところにあります。すると0~6(0/60~360/60)になるのですが、この値をガウス記号(床関数、floor)で切り捨ててあげちゃうんです。つまり、0、1、2、3、4、5のどれかにしちゃうというわけですが...、すると、この数字がそれぞれRYGCBMのどれかに当たることになります!なるほど、これで場合分けをすればいいのか...。ちなみにmod6(6で割ったときの余り)を求めているのは、色相が0~360ではない角度(1000度とか)になった場合を考慮してのものです。さてさて、この数字による場合分けを行ってしまえばめでたくHMKをRGBに変換することができるので、以下にソースを示します。

// HSVtoRGB変換
function HSVtoRGB(H, S, V) {
    var ret = [0, 0, 0];

    // 引数チェック
    if(S < 0 || S > 1 || V < 0 || V > 1) return ret;
    var Hi = Math.floor(H/60) % 6;
    var f  = (H/60) - Hi;

    // RGB
    V = V * 255;
    var p = Math.round(V * (1 - S));
    var q = Math.round(V * (1 - f*S));
    var t = Math.round(V * (1 - (1 - f)*S));

    V = Math.round(V);

    // 色相による場合わけ
    switch(Hi) {
        case 0: ret = [V, t, p]; break;
        case 1: ret = [q, V, p]; break;
        case 2: ret = [p, V, t]; break;
        case 3: ret = [p, q, V]; break;
        case 4: ret = [t, p, V]; break;
        case 5: ret = [V, p, q]; break;
    }
    return ret;
}
こんな感じで配列でRGBを返します。どうせならペイントみたいなインターフェースを作ってしまいましょう。これを頭に#をつけた16進数コードであらわされたRGBの文字列に変換する関数を付け加えます。
function RGBto16(RGB) {
     var R = (RGB[0] < 16) ? "0" + RGB[0].toString(16): RGB[0].toString(16);
    var G = (RGB[1] < 16) ? "0" + RGB[1].toString(16): RGB[1].toString(16);
    var B = (RGB[2] < 16) ? "0" + RGB[2].toString(16): RGB[2].toString(16);
    return ("#" + R + G + B);
}

function getClr(H, S, V) {
    return RGBto16(HSVtoRGB(H,S,V));
}
ここでtoString()を使いましたが、これは与えた引数を基数とした数字を返してくれます。16なので16進数のコードが返ってくるわけです。んー、便利。また、3項演算子を用いて、もし、15=fのように16進数に直した時1文字になってしまうとき、頭に0をつけて2文字にするようにします。この関数とHSVtoRGB()をまとめてやってくれるgetClr()を定義しました。この関数を使って色を設定します。表示部は以下のようになります。

これを書くと下のようになります。
ちなみに、上の例ではペイントに合わせるため、getClrの第3引数を(2+j)/3としました。もし、ここが1だった場合は下はグレーではなく真白になります。
さて、それではペイントライクなカラーピッカーの完成版のソースコードとサンプルを示します。
// パラメータ
di = 6;        // 横の刻み(0~360)
dj = 0.04;    // 縦の刻み(0~1)

// HSVtoRGB変換
function HSVtoRGB(H, S, V) {
    var ret = [0, 0, 0];

    // 引数チェック
    if(S < 0 || S > 1 || V < 0 || V > 1) return ret;
    var Hi = Math.floor(H/60) % 6;
    var f  = (H/60) - Hi;

    // RGB
    V = V * 255;
    var p = Math.round(V * (1 - S));
    var q = Math.round(V * (1 - f*S));
    var t = Math.round(V * (1 - (1 - f)*S));

    V = Math.round(V);

    // 色相による場合わけ
    switch(Hi) {
        case 0: ret = [V, t, p]; break;
        case 1: ret = [q, V, p]; break;
        case 2: ret = [p, V, t]; break;
        case 3: ret = [p, q, V]; break;
        case 4: ret = [t, p, V]; break;
        case 5: ret = [V, p, q]; break;
    }
    return ret;
}

// 16進数変換
function RGBto16(RGB) {
    var R = (RGB[0] < 16) ? "0" + RGB[0].toString(16): RGB[0].toString(16);
    var G = (RGB[1] < 16) ? "0" + RGB[1].toString(16): RGB[1].toString(16);
    var B = (RGB[2] < 16) ? "0" + RGB[2].toString(16): RGB[2].toString(16);
    return ("#" + R + G + B);
}

// まとめ
function getClr(H, S, V) {
    return RGBto16(HSVtoRGB(H,S,V));
}

// カラーコード取得用グローバル変数
_H = 0;
_S = 0;
// VBar変更
function changeVBar(H, S) {
    _H = H; _S = S;
    id=0;
    for(j=1; j>=0; j-=dj) {
        document.getElementById("b_"+ id).bgColor = getClr(H,S,j);
        id++;
    }
}

// カラーコード取得
function setClr(V) {
    with(document.getElementById("gtnClr")) {
        bgColor = getClr(_H, _S, V);
        style.color = getClr(_H, 0, 1 - V);
        innerHTML = getClr(_H, _S, V);
    }
}


<table class="color_table" border="0" cellpadding="0" cellspacing="0"> <script type="text/javascript"> for(j=1; j>=0; j-=dj) { document.write("<tr>"); for(i=0; i<360; i+=di) { document.write('<td onclick="changeVBar(' + i + ',' + j + ')" bgcolor="' + getClr(i,j,1) + '"> </td>'); } document.write("</tr>"); } </script> </table> <table class="color_table VBar" border="0" cellpadding="0" cellspacing="0"> <script type="text/javascript"> id = 0; for(j=1; j>=0; j-=dj) { document.write('<tr>'); document.write('<td id="b_' + id + '" onclick="setClr(' + j + ')" bgcolor="' + getClr(0,1,j) + '"> </td>'); document.write("</tr>"); id++; } </script> </table>
color

sample:
color
①でかい領域のどこかをクリック
②縦長の領域のどこかをクリック

って感じです。個人的には満足です。
しかしながら世の中は広いようで...、ライブラリを使ったりしてちょーすごいカラーピッカーもあります。

球体版(Color Sphere):
http://www.colorjack.com/software/dhtml+color+sphere.html

Saiみたいなやつ(JQuery color picker):
http://acko.net/dev/farbtastic

他にもYahoo!UIとかにもありましたし、フォトショライクなものもありました。ムムム、勉強しないと最先端にはなかなか追いつけませんね。

トラックバック(0)

このブログ記事を参照しているブログ一覧: JavaScriptによる色空間

このブログ記事に対するトラックバックURL: http://www.hecomi.com/mt/mt-tb.cgi/81