Tools

マンセル値を色コードに変換

計算

マンセル値
色コード Loading...
※N*の計算はできない。

RGBの計算方法

x, y, YからX, Zを計算する。Yはそのまま使う。

X = x * Y / y;
Z = (1 - x - y) * Y / y;

各々を100で割る。理由は知らない。わからない。

┌     ┐  ┌                     ┐┌   ┐
│ X_C │  │ 1/100    0      0   ││ X │
│ Y_C │=│   0    1/100    0   ││ Y │
│ Z_C │  │   0      0    1/100 ││ Z │
└     ┘  └                     ┘└   ┘

行列をかけてD65にする。D65が何ことかわからなくても良い。私にもわからない。

┌       ┐  ┌                                   ┐┌     ┐
│ X_D65 │  │   0.99727   -0.009397   -0.015411 ││ X_C │
│ Y_D65 │=│ -0.0010322    1.0008   0.00020888 ││ Y_C │
│ Z_D65 │  │      0           0       0.92095  ││ Z_C │
└       ┘  └                                   ┘└     ┘

行列をかけてXYZ→sRGB変換をする。

┌   ┐  ┌                           ┐┌       ┐
│ R │  │  3.2410  -1.5374  -0.4986 ││ X_D65 │
│ G │=│ -0.9692   1.8760   0.0416 ││ Y_D65 │
│ B │  │  0.0556  -0.2040   1.0570 ││ Z_D65 │
└   ┘  └                           ┘└       ┘

上の三つの計算は、行列の演算を先にしておけるので、まとめることができる。

┌   ┐  ┌                                                     ┐┌   ┐
│ R │  │ 0.0323373897428  -0.01569085597   -0.00509453853112 ││ X │
│ G │=│ -0.009684904912  0.018866083724    0.0005363972008  ││ Y │
│ B │  │  0.000556587808  -0.002046856732   0.0097254468688  ││ Z │
└   ┘  └                                                     ┘└   ┘

各々をy補正する。

R2 = (R <= 0.0031308) ? 12.92 * R : 1.055 * Math.pow(R, 1 / 2.4) - 0.055;
G2 = (G <= 0.0031308) ? 12.92 * G : 1.055 * Math.pow(G, 1 / 2.4) - 0.055;
B2 = (B <= 0.0031308) ? 12.92 * B : 1.055 * Math.pow(B, 1 / 2.4) - 0.055;

各々を0~255の値にする。

R3 = Math.max(Math.min(Math.round(255 * R2), 255), 0);
G3 = Math.max(Math.min(Math.round(255 * G2), 255), 0);
B3 = Math.max(Math.min(Math.round(255 * B2), 255), 0);

解説

Rochester Institute of Technologyの「Munsell Renotation Data」のページにデータファイル「all.dat」がある。h, V, C, x, y, YをJavaScriptに抜き出した。RGBの計算方法については、山本ワールドさんの「マンセル・カラー・システム」と「CIE1931xyz色度図」のページを参考にさせていただいた。

マンセル色相環は、5色(R, Y, G, B, P)とその中間の5色(YR, GY, BG, PB, RP)を基本色とし、各々をさらに10分割した計100色相で構成される。本来なら1R, 2R, ... , 9R, 10Rのように、ひとつの基本色に付き10個の色相が必要だと思うが、データには2.5R, 5R, 7.5R, 10Rのように4個しかない。しかも、明度や彩度別にちゃんとデータがあるが、不規則で、絵の具を混ぜて色を作り、それを測色計で測ったような雰囲気だ。データから1R, 2R, ... などを元の通りに再現できないと思う。

中間色を含め、データにないxyYについて、データから計算することにした。例えば「5Y 5/4.3」は「5Y 5/4」と「5Y 5/6」を 6 - 4.3 : 4.3 - 4 = 1.7 : 0.3 の比率で混ぜて作られる。さらに「5Y 5.6/4.3」は「5Y 5/4.3」と「5Y 6/4.3」を 6 - 5.6 : 5.6 - 5 = 0.4 : 0.6 の比率で混ぜて作られ、「5.7Y 5.6/4.3」は「5Y 5.6/4.3」と「7.5Y 5.6/4.3」を 7.5 - 5.7 : 5.7 - 5 = 1.8 : 0.7 の比率で混ぜて作られる。

当初、各データのRGBを計算しておいて、RGBの合成で中間色を計算する予定だったが、RGBの計算でy補正することがわかったので、中間色をxyYで合成してからRGBを計算する方法に切り替えた。

データの中には使えないものもある。色コードにするとき、RGB各色について0より小さくなる場合に0に、あるいは255より大きくなる場合に255にするようなデータがあって、マンセル値としては異なるのに、RGBの色コードにすると同じになってしまう色が多数ある。また、y = 0の場合は計算時にエラーとなってしまうため、RGBに変換できない。7.5PBのデータは何かおかしいと思う。

all.datのデータは小数点第五位で四捨五入されているようで、このデータでは正確な色コードを計算できないと思う。

マンセル色相環は物の色で、RGBは光の色なので、再現は難しい。RGBで表現された「5.7Y 5.6/4.3」の色を印刷しても、「5.7Y 5.6/4.3」の色にはならないだろう。色には難しいことが多いので、なるべく簡単に済ませ、あまりかかわらないほうが良い。

浮動小数点から整数への変換

すごく細かくて他愛ない話だけど、ずっと気になっているのでメモしておく。

色の成分(R, G, B)は、ディスプレイへの表示では0~255の整数で扱われるが、色の計算では0~1の浮動小数点にして使用されることがある。

double r = R / 255.0;

浮動小数点の0~1にするのは問題ないが、それを整数の0~255に戻すところでいつもモヤモヤする。だいたいこんなふうに書かれている。

int R = (int)(255 * r);

「R = 0」となるrの値は「0 ≤ r < 1/255」だ。他も同じように考えてみると、最後だけ不公平で、かわいそう。

r R
0 ≤ r < 1/255 0
1/255 ≤ r < 2/255 1
2/255 ≤ r < 3/255 2
253/255 ≤ r < 254/255 253
254/255 ≤ r < 255/255 254
r = 1 255

他はrに約1/255の幅があるのに、R=255になる場合だけ幅がなく、ぴったりr=1のときしかR=255にならない。切り捨てず四捨五入すれば良いと思うかもしれないが、そうすると今度はR=0とR=255になる場合のrの幅が他の半分になってしまい、不公平感をぬぐえない。できれば犠牲者を増やしたくない。

256をかけて、255を超える場合に255にするのが妥当かなと思う。

int R = Math.Min((int)(256 * r), 255));

あるいは、255.999みたいな数字をかければ、だいぶ不公平が改善される。

int R = (int)(255.999 * r);

ただ、どのみちRを調整する必要があるので、256をかけるのが妥当だろう。

int R = (int)(256 * r);
R = Math.Max(R, 0);
R = Math.Min(R, 255);

こんなふうにすべきだと思うが、計算式では255をかけるようだ。なぜなのだろう。


(2020/03/19 初稿)
(2020/03/28 更新)