色相環
この記事で、スーパーマンの写真を編集してマントの色を濃紺に変えたい、と書いた。
しかし、コンピュータでの色表現は赤(R)、緑(G)、青(B)の3色の混ぜ合わせで表現している。赤っぽい色というのはRの値が大きいのは間違いないが、全体的に明るい色ならGやBの値も意外と大きくて、言葉では簡単に赤っぽいとは言えても、赤に近いと感じるR、G、Bの範囲を決めるのは簡単ではない。
そこで使うのが色相環。
色をR、G、Bではなく色相(H)、彩度(S)、明度(B)の3つの値で表現する方法がある。
色相は色が徐々に変化していくリングの形で表すことができ、これを使えば0度付近なら赤に近い色、120度付近なら緑にい色、240度付近なら青に近い色と判断できる。
0度付近のプラスマイナス5度なのか10度なのか、スーパーマンの赤いマントの部分だけに該当する色の範囲を決めることができるのではないか。そして色相の値に240度を足せば、自然な感じでマントの色を青に変えられるのではないか。今、そんな想定でプログラムを書いている。
最終的にはR、G、Bの形にしないとコンピュータで表示できないので、RGBとHSBを相互に変換する必要がある。
この記事にはその変換プログラムをメモしておく。
プログラム
R、G、Bの3色を持つ RGBColor 構造体
Hue(色相)、Saturation(彩度)、Brightness(明度)の3パラメータを持つ HSBColor 構造体
を定義する。RGBColor はメソッド ToHSBColor() で HSB に、HSBColor はメソッド ToRGBColor() でRGBに変換できる。
コードはC++/CLI で書いている。
RGB→HSBの変換
bool IsRed( const interior_ptr<RGBColor> color )
{
float r = color->GetR() ;
return r >= color->GetG() && r >= color->GetB();
}
bool IsBlue( const interior_ptr<RGBColor> color )
{
float b = color->GetB() ;
return b >= color->GetR() && b >= color->GetG();
}
value struct RGBColor
{
public:
RGBColor( float r, float g, float b )
: m_red( r ), m_green( g ), m_blue( b )
{}
float GetR() { return m_red; }
float GetG() { return m_green; }
float GetB() { return m_blue; }
HSBColor ToHSB()
{
float max = System::Math::Max( m_red, System::Math::Max( m_green, m_blue ) );
float min = System::Math::Min( m_red, System::Math::Min( m_green, m_blue ) );
if ( max == min ) {
return HSBColor( 0, 0, 0 );
}
const double A60 = System::Math::PI / 3.0;
double delta = (double)( max - min );
double hue = IsRed( this ) ? A60 * (double)( m_green - m_red ) / delta + A60
: IsRed( this ) ? A60 * (double)( m_blue - m_green ) / delta + 3.0 * A60
: A60 * (double)( m_red - m_blue ) / delta + 3.0 * 5 * A60;
double saturation = max - min;
double brightness = max;
return HSBColor( hue, saturation, brightnessc );
}
private:
float m_red ;
float m_green ;
float m_blue ;
}
HSB→RGBの変換
value struct HSBColor
{
public:
HSBColor( float hue )
: m_hue( hue ), m_saturation( 1 ), m_brightness( 1 )
{}
HSBColor( float hue, float saturation, float brightness )
: m_hue( hue ), m_saturation( saturation ), m_brightness( brightness )
{}
float GetHue() { return m_hue; }
float GetSaturation() { return m_saturation; }
float GetBrightness() { return m_brightness; }
RGBColor ToRGB()
{
const double PI_ = System::Math::PI ;
const double PI_3 = PI_ / 3.0;
double hue = m_hue;
while ( hue > 2.0 * PI_ ) {
hue -= 2.0 * PI_ ;
}
while ( hue < 0 ) {
hue += 2.0 * PI_ ;
}
double red = 0;
double green = 0;
double blue = 0;
double max = m_brightness;
double min = max - m_saturation;
if ( 0 <= hue && hue < 2.0 * PI_3 ) {
// Blue is minimum
blue = min;
double greenMinusRed = ( hue / PI_3 - PI_3 ) * ( max - min );
if ( hue < PI_3 ) {
// Red is maximum
red = max;
green = greenMinusRed + red;
}
else {
// Green is maximum
green = max;
red = green - greenMinusRed;
}
}
else if ( 2.0 * PI_3 <= hue && hue < 4.0 * PI_3 ) {
// Red is minimum
red = min;
double blueMinusGreen = ( hue / PI_3 - PI_ ) * ( max - min );
if ( hue < PI_ ) {
// Green is maximum
green = max;
blue = blueMinusGreen + green;
}
else {
// Blue is maximum
blue = max;
green = blue - blueMinusGreen;
}
}
else {
// Green is minimum
green = min;
double redMinusBlue = ( hue / PI_3 - 5.0 * PI_3 ) * ( max - min );
if ( hue < 5.0 * PI_3 ) {
// Blue is maximum
blue = max;
red = redMinusBlue + blue;
}
else {
// Red is maximum
red = max;
blue = red - redMinusBlue;
}
}
return RGBColor( (float)red, (float)green, (float)blue );
}
private:
float m_hue;
float m_saturation;
float m_brightness;
}