Tools
マンセル値を色コードに変換
計算
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 更新)