C#によるHSV(HSB)値からRGB値への変換

作成したプログラムの実行画面

(ソース: sample_source | 実行ファイルのみ: sample_bin)

1.はじめに
 HSV値をRGB値へ変換するプログラムをC#で実装した。変換する際のアルゴリズムは、ネットで検索すればすぐ見つかるが、せっかくなので自分で考えてみることにした。

以下に、簡単ではあるが今回使用した開発環境を記す。
・Windows 8 Pro
・.NET Framework 4.5
・Visual Studio Ultimate 2013 Preview
2.HSV(HSB)値とは
 HSV値とは、色を指定する際に用いる、
   ・色相(Hue)
   ・彩度(Saturation)
   ・明度(Value)
の3つのパラメータのことを言う。図1は、これら3つのパラメータによる色空間を立体的に描画したものである。

図1.HSVによる色空間を立体的に描画

2.1色相
 虹は、赤や橙、黄や緑など、7つの色で表される。このような、区別できる色あいのことを色相と言う。色相と今回変換するRGB値との関係を図2に示す(図2のRGB値は、明度・彩度が共に1.0のもの)。色相の値Hは、0°≦ H < 360°の角度で指定される。H = 0°の時、赤色(RGB値では(255, 0, 0))で、その後、黄、緑、青…と変化していき、一周(H = 360°)したとき、再び赤色に戻る。

図2.色相におけるRGB値との関係

2.2彩度
 彩度は、色の鮮やかさを表すもので、0.0~1.0の値で指定する。図1では、S方向が彩度の変化である。彩度が中央のゼロに近づくにつれ、無個性の色(黒から白のグレースケール)に変化するのを見て取れる。


2.3明度
 明度は、色の明るさを表すものである。これも、0.0~1.0の値で指定する。0.0が最も暗く、1.0に近づくにつれて明るくなる。図1では、V方向が明度の変化である。
3.アルゴリズムを考える
 HSV値からRGB値への変換は、明度、色相、彩度の順番で考える必要がある。以下その理由を含め、説明する。

3.1明度を求める
 図1を見ると、彩度の値がゼロのとき、円柱の中心を通る軸の色は、グレースケールになっている。グレースケールの黒から白へ変化する時のRGB各値は、

(0, 0, 0) → … → (127, 127, 127) → … → (255, 255, 255)

のように、同じ数値が並ぶ。C#では、RGBで色を指定する場合、最小で0から、最大255の整数を使う。つまり、円柱の中心に沿って変化するグレースケールの割合が、ちょうど明度の値と一致する(例えば、グレースケールの値が127の場合、これは全体(255)の50%で、明度の0.5と等しい)。
 また、色相及び彩度の値の変化は、0から最大でグレースケールで指定される値の範囲で行われる。そのため、一番最初に明度を使って、最大値を出しておく必要がある。

3.2色相を求める
 色相は、RGB値で色を作る上で、それぞれの配合比率を決定する。色相値の変化に従って、RGB各値は、規則的に変化をしている。その規則の内容を抜き出したのが、図3である。図2も合わせて見てみると、RGB各値の最大値を255とすると、値が0(最大値の0%)の時が120°間続き、その後、60°間で0から255(0%から100%)へ変化する。今度は、255(100%)が120°間続いたのち、最後の60°間は、255から0(100%から0%)へ変化する。この繰り返しである。

図3.RGB各値の変化の規則

 そこで、上記のような変化に対し、図3の下に描かれているような、角度を対応させる。そして、色相の値Hが0°≦ H < 120°の時は0%、150°の時は50%、180°≦ H < 300°の時は100%…、となるような値を返す関数を考える。しかし、このままでは、色相で指定された角度と、RGB値の赤色とでは、+240°、緑色では+120°のズレがある。そこで、指定された色相の角度Hに対する、赤色Rは、H + 240°、緑色GはH + 120°とすることで求めることができる。
 次に述べる彩度を設定する際、色相を予め決めておく必要があるため、2番目に決定しなければならない。


3.3彩度を求める
 彩度の特徴を見ると、1.0から0.0へ変化していくと、明度で決定された、最大値へRGB各値が近づく。その変化の様子を描いたものが、図4である。ここでは、色相を100°、明度を1.0に固定している。

図4.彩度の違いによるRGB値の変化

 まず、B(青色)の値の変化に注目する。Bの最初の値は0であり、彩度が0.5(100%から50%へ)減ると、最初の値(0)から+127変化する。この変化量は、最大値と最初の値0との差の0.5倍に等しい。同じことが、R(赤色)でも言える。Rの最初の値は85であり、彩度が0.5(100%から50%へ)減ると、最初の値(85)から+85の170へと変化する。つまり、彩度の変化によるRGB各値は、

RGB各値 = 最初の値 + 変化量
    = 最初の値 + (最大値 - 最初の値) × (1.0 - 彩度)

で求めることが出来る。図4の値を実際に代入して計算することで、この式が正しいことが確かめられる。
4.実装
 上記のアルゴリズムは、2つの関数に分けて実装した。コード1で記述されている関数は、3.2のところで書かれた、色相を求めるための、角度に対する割合を返す関数である。メインは、コード2で記述されている関数である。
        private double getPercent(double d) {
            // 角度が負の場合は、正の値に直して代入する
            d = d < 0 ? 360 + (d % 360d) : d;
            // 角度の値が360以上の場合、値の範囲を0以上360未満にして代入
            d %= 360;
            if (d >= 0 && d < 120) return 0.0;
            else if (d >= 120 && d < 180) return (d - 120d) / 60d;
            else if (d >= 300 && d < 360) return 1d - ((d - 300d) / 60d);
            else return 1.0;
        }
 コード1では、引数となる数値は角度であり、常識的な範囲の実数なら何でもいい(マイナスの角度や360°を超えていても大丈夫)。3行目と5行目で、0 ≦ d < 360 になるように変換している。その後は、図3の定義に従ってif構文で値を振り分けている。
        private Color getHSVtoRGB(double h, double s, double v) {
            // 明度から各RGB値の最大値を取得
            double RGBmax = 255.0 * v;
            // 色相からRGBの取得
            double R = RGBmax * getPercent(h + 240);
            double G = RGBmax * getPercent(h + 120);
            double B = RGBmax * getPercent(h);
            // 各RGB値に彩度を設定する
            R += (RGBmax - R) * (1.0 - s);
            G += (RGBmax - G) * (1.0 - s);
            B += (RGBmax - B) * (1.0 - s);
            return Color.FromArgb((int)R, (int)G, (int)B);
        }
 メインとなるコード2では、引数であるHVSの値が指定されると、RGB各値に変換した後、直接Color構造体として返すようにした。3行目で、明度により最大値を決定し、5行目~7行目で、色相に対するRGB各値の配合を決定する。最後に、9行目~11行目で、RGB各値に彩度を設定している。
5.結果
 図5に以上のアルゴリズムを使って作られた、表を示す。図5の右側にある、虹色のグラデーションボックスは、彩度が1.0、明度が1.0、色相Hが左から右へ、0° ≦ H < 360°変化したものである。左側の表は、このグラデーションボックスの彩度と明度を、0.0から1.0まで0.1ずつ変化させたものを並べたものである。

図5.今回考えたアルゴリズムを使って作られた出力結果

ホームへ
現在の閲覧者数:
アクセス数: inserted by FC2 system