はじめに
普段コンピュータなんかでは光の三原色であるRGB(A)色空間を扱うことが多いと思います。
対して、色の三原色はCMYとされていますが、美術などでは原色として「赤」「青」「黄」を取り扱うRYBを使用したりもします。
CMYが提案される以前は画家も原色として赤青黄を伝統的に使っていたようで、
この辺りはいろいろ歴史もあるようです。
絵具を混ぜたりカラー印刷で色インクを併置するときに行われる減法混合の場合の三原色は、シアン・マゼンタ・イエロー(黄色)の三色である[2]。なお、この主張がなされた1915年よりも前に画家たちは、伝統的に、赤青黄を減法三原色としていた。
原色論争
さて、私が作っていたゲームでは、この「赤」「青」「黄」を原色として扱うものだったので
「赤」と「青」を合わせると「紫」に、
「赤」と「黄」を合わせると「橙」に
「青」と「黄」を合わせると「緑」に
「赤」と「青」と「黄」を合わせると「黒」になるように、したかったのですが
通常のRGB(CMY)でこれを扱うのは難しそうなので
RYB色空間を使用する必要がでてきました。
調査、そして論文発見
調査したところ、何年か前にコンピュータ上でRYB色空間を扱うための
RGBとRYBの相互変換を定式化したものが論文で発表されていました。
今回自分もこちらのモデルを使用してみることにしました。
RGB | RYB | |
---|---|---|
白 | (1,1,1) | (0,0,0) |
赤 | (1,0,0) | (1,0,0) |
黄 | (1,1,0) | (0,1,0) |
青 | (0,0,1) | (0,0,1) |
橙 | (1,0.5,0) | (1,1,0) |
紫 | (0.5,0,1) | (1,0,1) |
緑 | (0,1,0) | (0,1,1) |
黒 | (0,0,0) | (1,1,1) |
C++での実装
struct RGB
{
double r;
double g;
double b;
};
struct RYB
{
double r;
double y;
double b;
};
RGB から RYB への変換
/// <summary>
/// RGB から RYB への変換
/// </summary>
constexpr RYB RGB2RYB(const RGB& rgb)
{
auto [r, g, b] = rgb;
// 白成分を取り除く
const double Iw = std::min(r, std::min(g, b));
const double rRGB = r - Iw;
const double gRGB = g - Iw;
const double bRGB = b - Iw;
// RGBでのY成分 (RとGの共通成分がYなのでmin)
const double yRGB = std::min(rRGB, gRGB);
// RGBのR成分からRGBのY成分だけ取り除いたのがRYBのR成分
const double rRYB = rRGB - yRGB;
// 同様に RGBのGからRGBのY成分だけ取り除いたものがG成分として余る
// gRGB' = gRGB - yRGB
// 青と緑を合わせる際に最大値を越えないように2で割って調整
// gRGB'' = gRGB' / 2
// bRGB' = bRGB / 2
// G成分はRYBにおいて、YとBの合成色なので、これを黄と青に分配している
const double yRYB = (yRGB + gRGB) / 2.0; // yRGB + gRGB''
const double bRYB = (bRGB + gRGB - yRGB) / 2.0; // bRGB' + gRGB''
// 正規化
const double maxRGB = std::max(rRGB, std::max(gRGB, bRGB));
const double maxRYB = std::max(rRYB, std::max(yRYB, bRYB));
const double n = maxRYB == 0 ? 1.0 : maxRGB / maxRYB;
const double rRYB_ = rRYB * n;
const double yRYB_ = yRYB * n;
const double bRYB_ = bRYB * n;
// 黒成分を足す
const double Ib = std::min(1 - r, std::min(1 - g, 1 - b));
return RYB{
.r = rRYB_ + Ib,
.y = yRYB_ + Ib,
.b = bRYB_ + Ib,
};
}
RYB から RGB への変換
/// <summary>
/// RYB から RGBへの変換
/// </summary>
constexpr RGB RYB2RGB(const RYB& ryb)
{
auto [r, y, b] = ryb;
// 黒成分を取り除く
const double Ib = std::min(r, std::min(y, b));
const double rRYB = r - Ib;
const double yRYB = y - Ib;
const double bRYB = b - Ib;
// RYBのG成分
const double gRYB = std::min(yRYB, bRYB);
const double rRGB = rRYB + yRYB - gRYB;
const double gRGB = yRYB + gRYB;
const double bRGB = 2.0 * (bRYB - gRYB);
// 正規化
const double maxRYB = std::max(rRYB, std::max(yRYB, bRYB));
const double maxRGB = std::max(rRGB, std::max(gRGB, bRGB));
const double n = maxRGB == 0 ? 1.0 : maxRYB / maxRGB;
const double rRGB_ = rRGB * n;
const double gRGB_ = gRGB * n;
const double bRGB_ = bRGB * n;
// 白成分を足す
const double Iw = std::min(1 - r, std::min(1 - y, 1 - b));
return RGB{
.r = rRGB_ + Iw,
.g = gRGB_ + Iw,
.b = bRGB_ + Iw,
};
}
RGB→RYB変換式について
変換式の意味については、論文の付録で紹介されていました。
白成分を取り除いてから、黒成分を最後に加えるのは、
RGBが加法混合なのに対してRYBが減法混合なので、これを変換するためです。
yRGB = min(rRGB, gRGB)
RGB空間におけるY成分は、黄が赤と緑の合成なので、RとGの最小値分だけ含まれていると言えます。
rRYB = rRGB - yRGB
gRGB' = gRGB - yRGB
RYB空間ではY成分は別軸で持つので、RGBでのR成分から先ほど出したY成分を引いたものが、RYBでのR成分となります。
同様にG成分もRGBのG成分からY成分を引いたぶんだけ余っています。
gRGB'' = gRGB' / 2
bRGB' = bRGB / 2
yRYB = yRGB + gRGB''
bRYB = bRGB' + gRGB''
RYBで緑は、黄と青の合成なので、余っていたG成分をY成分とB成分にも分配します
この時、青と緑を足す際に最大値を越えないように2で割って調整しています
参考
宣伝
ColorfulTone
今回の題材を取り扱うきっかけとなった
色をテーマにした音楽ゲームです。是非遊んでください