JavaFX の Color クラスを探る

この記事は JavaFX Advent Calendar 2016 の初日です。誰も書く気配を見せないので、最近の関心事について少しだけ。

JavaFX の Color クラスは、API ドキュメントにも記載があるように sRGB 色空間をモデリングしたものです。実際に、内部実装は red (赤)、green (緑)、blue (青)、opacity (不透明率) の 4 つの 'float' 型フィールドで構成されています (外部的には double 型でのやり取りとなります)。AWT の Color クラスと同様に HSB (HSV) 色空間との相互変換もサポートしますが、使う側としては変換操作を意識することなく、必要に応じて sRGB または HSB として利用することができます。また、HTML/CSS のカラー属性文字列からインスタンスを生成できたり、148 色のプロセスカラーを事前定義するなど、AWT と比較して Web との親和性も強化されています。

AWT と異なり、CIE XYZ 表色系などへの変換はサポートされていません。色の世界は奥が深いため、にわか知識で様々な表色系に手を出すよりは、sRGB と HSB を確実に押さえようという考えでしょうか。@yumix_h が小惑星の色表現で悲鳴を上げているのを見て、色について少し調べてみたのですが (大学で環境工学を専攻していたため空間の色に対する予備知識が救いとなりました)、基準となる光源の選び方やガンマ補正など、迂闊に手を出すと大やけどをする分野でもあります。

Color クラスのインスタンスは以下の方法で生成することができます。

  • コンストラクタ -- sRGB: Red (赤)、Green (緑)、Blue (青)、Opacity (不透明度・オプション)
  • color ファクトリ・メソッド -- sRGB: Red (赤)、Green (緑)、Blue (青)、Opacity (不透明度・オプション)
  • rgb ファクトリ・メソッド -- RGB: Red (赤・離散値)、Green (緑・離散値)、Blue (青・離散値)、Opacity (不透明率・オプション)
  • hsb ファクトリ・メソッド -- HSB: Hue (色相)、saturation (彩度)、brightness (明度)、Opacity (不透明度・オプション)
  • gray ファクトリ・メソッド -- Gray (無彩色の明度)、Opacity (不透明度・オプション)
  • grayRgb ファクトリ・メソッド -- Gray (無彩色の明度・離散値)、Opacity (不透明度・オプション)
  • web ファクトリ・メソッド -- HTML/CSS カラー属性文字列 (rgb、rgba、hsl、hsla)
  • 定数 - プロセスカラー (148 色)

色成分の取得は以下のメソッドで行います。sRGB ↔ HSB 変換を明示的に行うことなく双方の色成分を取得できることが特徴です。具体的には sRGB で作成した色の色相・彩度・明度を取得したり、反対に HSB で作成した色の赤・緑・青成分を取得することが可能です。

  • double getRed() -- 赤 (0.0-1.0)
  • double getGreen() -- 緑 (0.0-1.0)
  • double getBlue() -- 青 (0.0-1.0)
  • double getOpacity() -- 不透明度(0.0-1.0)
  • double getHue() -- 色相 (0.0-360.0)
  • double getSaturation() -- 彩度 (0.0-1.0)
  • double getBrightness() -- 明度

相対的な色変換

「やや明るく」「やや暗く」のような相対的な色変換を行うメソッドが用意されています。

  • Color brighter() -- より明るくする
  • Color darker() -- より暗くする
  • Color saturate() -- 彩度を上げる
  • Color desaturate() -- 彩度を下げる

最初、仕組みがよく分からなかったのですが、内部で deriveColor という色相・彩度・明度をシフトするメソッドを呼んでいるようです。

Color deriveColor(double hueShift, double saturationFactor, double brightnessFactor, double opacityFactor)

ちなみに、色相変化量を 0、彩度・明度および不透明度を 1.0 (倍) に設定すると、全く同じ色になります。

deriveColor(0, 1.0, 1.0, 1.0)

前掲のメソッドは、それぞれ次の deriveColor 呼び出しと等価です。

  • brighter() → deriveColor(0, 1.0, 1.0 / 0.7, 1.0)
  • darker() → deriveColor(0, 1.0, 0.7, 1.0)
  • saturate() → deriveColor(0, 1.0 / 0.7, 1.0, 1.0)
  • desaturate() → deriveColor(0, 0.7, 1.0, 1.0)

彩度・明度を上げるときは約 143 % (1.0 / 0.7)、下げるときは 70 % (0.7) に設定しているようです。

グレースケール化と色反転

特定の色をグレースケールにしたり、あるいは反転することも可能です。

  • Color grayscale() - グレースケール化
  • Color invert() - 色反転

最初、これらについても deriveColor メソッドを使用しているのかと推測しましたが、実際には直接 sRGB の色成分を変更しているようです。グレースケール化の場合は、以下に示すように、赤・緑・青それぞれに異なる重み付けをして、すべての成分を同じ値に設定し直しています。緑のウェイトが大きいのは、CIE XYZ 表色系の Y 成分 (緑に相当) が色成分に加えて可視光のパワーも反映していることと関連があると思われます。また、人間の目は青よりも赤に対する感度の方が高いことが、それぞれの重み付けにも影響しています。

  • grayscale() → color(0.21 * red + 0.71 * green + 0.07 * blue, 0.21 * red + 0.71 * green + 0.07 * blue, 0.21 * red + 0.71 * green + 0.07 * blue, opacity);

色反転の場合、赤・緑・青すべての成分を単純に反転させています。色相を 180°回転させるのかと想像していただけに、意外な実装です。

  • invert() → Color.color(1.0 - red, 1.0 - green, 1.0 - blue, opacity)

sRGB は基本的に D65 光源 (色温度 6500K) における白色を基準とした色の表現ですが、D50 光源 (色温度 5000K) に対応した sRGB も存在しているようです。また sRGB では使用しませんが C 光源 (色温度 7000K) を使用する色表現や、sRGB と同じ D65 光源を採用しつつも補正値が異なる Adobe RGB なども存在するわけで、それらに対する拡張ポイントも欲しかったというのが正直な感想です。

参考まで、http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html には CIE XYZ 表色系における光源ごとの変換行列が掲載されています。C、D50、D65 以外にも様々な光源が存在していると分かります。

p.s. @yumix_h は光源の違いについてもちゃんと考慮しているのだろうか?