プログラミングII

プログラミング II
講義ノートそのなな
伊藤希
平成 22 年 11 月 30 日
はじめに
1
良いプログラミングを行なう条件は、対象領域を十分に理解していることと、良いプログラムに
触れていることである。Java 言語には豊富なクラスライブラリがソースコードも含めて付属して
おり、それを活用しない手はない。ここでは JColorChooser のカスタマイズを例として、ライブ
ラリのソースコードを読み解き、カスタマイズを行なう。
コンピュータでの色表現
2
コンピュータのディスプレイでは、液晶にしろ陰極線管にしろ、色は赤 (R)、緑 (G)、青 (B) の
三原色に分解して表示されている。色表現のモデルには、この他にシアン (C)、マゼンタ (M)、イ
エロー (Y)、黒 (K) を用いるものや、色相 (Hue)、彩度 (Saturation)、明度 (Brightness) による
ものがある。コンピュータでの色指定には、これらのパラメータの組のいずれかを指定する必要が
ある。
一方、グラフィカルユーザインタフェース (GUI) で視覚的に色選択をする場合、コンピュータ
のディスプレイが二次元であるために 3 ないし 4 変数のうち二つまでしか表示できない。このた
め、最大二つまでの変数を選択可能とし、他のパラメータは固定したユーザインタフェースが用
いられる。たとえば JColorChoore の HSB モード (図 1) では H、S、B のうち一つをラジオボ
タンで選択してスライダーに配置し、それ以外を二次元インタフェースで選べる様になっている。
このインタフェースは、CIE の色度図に比べると直観的にはわかりづらいものとなっている。一
方、RGB モードではスライダーが三つあるだけであり、これもわかりやすいとは言えない。結局
は RGB の三成分混合系であるから、三成分組成図同様の三角表示に明度分を組み合わせたもので
表現できる筈である。sRGB がこれに相当し、また HSB モードの B をスライダーに配置して残
りを正三角形に投影したものと同等である。そこで、JColorChoore をカスタマイズして、この三
角形 GUI を追加してみよう。
演習 1
三原色が赤、緑、青なのは何故か。CMYK の K は何の略か。
1
図 1: JColorChoore の HSB モード
図 2: 「こんなのを作ってみたい」sRGB モード
3
JColorChooser の裏を読む
JColorChooser にはデフォルトで Swatches、HSB、RGB の三つのパネルがある。これらのカラー
チューザーパネルの追加と削除メソッドとして、addChooserPanel(AbstractColorChooserPanel)
と removeChooserPanel(AbstractColorChooserPanel) が用意されている。ここから、これら
メソッドの加除対象である AbstractColorChooserPanel のサブクラスを実装してやれば目的の
カラーチューザーを作成できるとわかる。JColorChooser の HSB パネルクラスを継承して作成
するのが早道に思えるが、javax.swing.colorchooser パッケージの API ドキュメントを見ても
それらしいクラスは見当たらない。どこで定義されているのだろうか?
クラスライブラリのソースコードを調べてゆくと、javax.swing.colorchooser パッケージ
に AbstractColorChooserPanel のサブクラスとして DefaultHSBChooserPanel がある。いか
にもそれらしいクラス名である。JColorChooser ではこれが使われているのだろうか? しかし
JColorChooser のソースコードにはその様な名前は登場しない。そこで、grep を使ってどこで
DefaultHSBChooserPanel が使われているか調べてみよう。おそらく javax.swing 以下にあるだ
ろうから、Java の標準クラスライブラリ (JDK に付属する src.zip) を展開したディレクトリで
$ grep DefaultHSB javax/swing/*.java
$ grep DefaultHSB javax/swing/*/*.java
2
などとしてみよう。すると ColorChooserComponentFactory が DefaultHSBChooserPanel のイ
ンスタンスを生成していることがわかる。次に ColorChooserComponentFactory がどこで使われ
ているかを調べてみよう。
$ grep ColorChooserComponentFactory javax/swing/*.java
$ grep ColorChooserComponentFactory javax/swing/*/*.java
$ grep ColorChooserComponentFactory javax/swing/*/*/*.java
と順次 grep で調べてゆくと、ColorChooserComponentFactory を使っているクラスとしては
javax.swing.plaf.basic パッケージの BasicColorChooserUI 及び javax.swing.plaf.synth
パッケージの SynthColorChooserUI が見つかる筈である。これらのソースコードを読めばわか
る通り、SynthColorChooserUI は BasicColorChooserUI の、また BasicColorChooserUI は
ColorChooserUI のサブクラスであり、JColorChooser#getUI() の記述からこれらのクラスが
JColorChooser のルック&フィールを決めていることがわかる。こうした UI の委譲は Swing の
基本であり、外見をカスタマイズする場合の手法として記憶の隅にとどめておくとよい。
DefaultHSBChooserPanel が目的のクラスであるとわかったのでこれを拡張できるとよいのだ
が、残念ながらスコープがパッケージなので javax.swing.colorchooser の外では継承できない。
再コンパイルして独自ライブラリを javax.swing.colorchooser に追加することは不可能ではな
いが、互換性を考えると別途実装すべきである。そこで、DefaultHSBChooserPanel を真似て実
装することになる1 。
3.1
DefaultHSBChooserPanel を読む
真似るべき DefaultHSBChooserPanel の中身をみてゆこう。クラス定義冒頭で private メン
バとして HSBImage のインスタンスが transient として宣言されている。transient ということ
は、シリアライズ時に保存されない、つまり動的に生成されるインスタンスであるということであ
る。JColorChooser の動作からすれば、ラジオボタンでスライダーに指定されたパラメータによっ
て描画されるべき画像は異なるから、当然動的生成されるはずである。こうした点から、HSBImage
が目的の画像クラスであると目星をつけられる。
では HSBImage はどこで定義されているのか、と探すと、DefaultHSBChooserPanel.java を読
むと、DefaultHSBChooserPanel の内部クラスとして SyntheticImage を継承して定義されている。
SyntheticImage は SyntheticImage.java で定義されているが、いずれも javax.swing.colorchooser
パッケージでのみ参照可能である。再利用するとすれば、勝手に javax.swing.colorchooser だ
としてコードを書く必要がある。まず javadoc で処理して API を抽出しておこう。これらのクラ
スには public メソッドも protected メソッドも定義されていないため、オプションなしで処理
してもエラーになるだけである。javadoc に pcakage オプションと private オプションを与えて
API ドキュメントを生成し、それを眺めながらコードを根気よく読んでゆこう。
HSBImage は SyntheticImage を継承しているので、まず SyntheticImage からみてゆこう。
SyntheticImage は ImageProducer を実装しており、メソッドのうち ImageProducer 以外のもの
に着目すると、コンストラクタの他には
• protected void computeRow(int, int[])
• protected boolean isStatic()
1 でも結局
colorchooser パッケージの一部として実装しちゃったりするんだな、授業時間の制約があるから
3
• public void nextFrame(int)
の三つがある。isStatic() は名前からすれば静的かどうかを調べるための関数であろう。nextFrame(int)
は動的な場合に実装されるべき関数であって、SyntheticImage な空の関数である。結局、読むべ
き関数は computeRow(int, int[]) だけである。コメントを読むとこの関数だけをオーバーライ
ドすればよい、とあり、例示もまた実際のコードもグレースケールで塗りつぶすコードになって
いる。
これら三つのメソッドは、HSBImage ではどう実装されているだろうか。isStatic() は false を返
す様になっており、nextFrame(int) にもそれに応じた修正がなされている。肝腎の computeRow(int, int[])
は動的生成に対応した処理をした後で getRGBForLocation(int, int) を繰返し呼び出してイメー
ジ生成を行なっており、getRGBForLocation(int, int) は getHSBForLocation(int, int, float[])
を呼び出している。HSBImage は HSB パレットだけでなくスライダーでも使われているので、
getHSBForLocation(int, int, float[]) 内部では case 文によりパレットの場合とスライダー
の場合それぞれに対応する様になっている。
これで大体の様子はわかった。方針としては、
• SyntheticImage を継承して SRGBImage を作る
• DefaultHSBChooserPanel を継承するか、真似るかして SRGBChooserPanel を作り、HSBImage
の代わりに SRGBImage を使う様にする
の二段階が最短距離だろう。では実際にこれらのクラスを実装しよう。
カスタム ChooserPanel の設計と実装
4
まず、DefaultHSBChooserPanel に対するおおまかな変更点を考えよう。
• 色選択パレットを正三角形にする
• グレースケールスライダーの値に応じて色選択パレットの大小を変える (?)
• RGB 表示窓はそのまま、HSB 表示窓とラジオボタンは不要
二つは SRGBImage、一つは SRGBChooserPanel の変更点である。
4.1
SRGBImage の設計と実装
まず決めなければならないのは正三角形の向きと大きさ、それにスライダーの大きさである。三
角形の向きは CIE 色度図同様に、頂点が G、右下が R、左下が B で良いだろう (この配置はなぜ
合理的か?)。RGB 各色 8 ビットを仮定すると、スライダーは黒すなわち (r, g, b) = (0, 0, 0) から
√
白 (r, g, b) = (255, 255, 255) までに対応する。白のノルムをとると 256 3 ∼ 444 であるが、実
際には無彩色ならば各色とも同じ値をとるので 256 階調しかない。一方、正三角形側に要求され
る分解能を考えると、高さがその方向の色強度に対応するから、最大高 255 の正三角形が描画さ
れる様にすればよい。ここで注意する必要があるのは、スライダーの最大値が正三角形の最大サイ
ズを意味しないということである。等明度球面を考えると、三角形が最大となるのはスライダーが
√
2562 /3 ∼ 148 となる時であって、それよりも明度が高い場合には三角形は縮小することになる。
4
これでうまくゆくだろうか? 実はうまくゆかない。考えている色空間は RGB 各色 256 階調であ
り、モデルとしてはそれぞれの RGB 値に対応した立方体の積木を積み重ねて三角錐を作ったもの
だと考えればよい。一番小さい三角錐は、暗い方からはじめると、(0, 0, 0) つまり黒く塗られた
積木一つである。その次の段階はそのまわりに (1, 0, 0)、(0, 1, 0)、(0, 0, 1) に塗られた積木をひ
とつずつ置いた状態であり、この三つの積木が見えていることになる。見た目としては赤と青の積
木の上に緑の積木を置いた様に見える筈である。これをコンピュータディスプレイ上に表示するの
に必要なのは、2 × 2 ピクセルの領域ではなくて、4 × 4 ピクセルの領域である。もし 2 × 2 の領
域で済ませようとすると、断面を斜めに見ることになる。それでも必要な色指定はできるのだが、
美しくない! しかし、対称性のために 256 × 256 で済む画像を 512 × 512 で表示するのも馬鹿げて
いる。偶数個の立方体と奇数個の立方体を交互に積むのだから、一つの手は、一段ごとに右そろえ
と左そろえに切替えるやりかたである。さすがに二段の場合はみっともないことになるが、ある程
度の段数以上ならそれなりの出力が得られる (実際、方眼紙に描いてみよ)。高さ方向が 18% 増し
になるため正三角形にはならないが、CIE 色度風だと納得することにしよう。このやりかたなら、
一段ごとに G の値を 1 ずつ変えるだけだから、実装も簡単ですむ。
実はこれでもまだ不十分である。対象としているのは RGB 各色 256 階調の色立方体であり、上
の議論が成り立つのは R + G + B = 256 平面の暗側領域だけである。R + G + B = 512 平面の
明側領域では逆三角形になり、この二平面の間では投影図形は六角形である2 。とはいえ、R + G
+ B = 256 平面の暗側領域以外でも描画が異なるだけで基本処理は同じであるから、まずは R +
G + B = 256 平面の暗側領域を実装し、残りは演習問題としよう。
内部状態としては何が必要だろうか。スライダーの設定値で描画すべき画像が変わるのだから、
スライダーの設定値が必要だろう。また、スライダーの値で決まる三角形の底辺の長さと高さ (こ
の二つは一致する) も必要だろう。毎回スライダーから値を読み出し、その値から三角形のジオメ
トリを毎回計算することも可能であるが、スライダーの値が変わらない限り無駄な読み出しと計算
である。スライダーの値変更は別途イベントとしてやりとりすることにしよう。変数名は、わかり
やすければ何でもよい。強度と底辺それぞれに intensity と base を用いる。三角形を表示する際、
底辺を固定する方法と重心を固定する方法とが考えられる。どちらにも対応できる様に、三角形底
辺の描画領域下辺からの高さを保持する変数 bottom も用意しておこう。
スライダーの値に応じて三角形の大きさが変わるのだから、三角形の内側と外側で違う処理をす
る必要がある。用意すべきメソッドは computeRow(int, int[]) であり、第一引数で指定される
ラスタを第二引数の配列に書くのであるから、三角形よりも上のラスタ、三角形よりも下のラスタ、
三角形を含むラスタ、の三通りに分けて考える必要がある。この三領域は、第一引数が 0 と height
- bottom - base の間にある場合、base と height の間にある場合、それ以外の場合、に対応する。
三角形の外側ではアルファ値を 0 に指定して背景を見せてやればよいから、コードは図 3 の様な
ものになるだろう。アルファ値が 0 なので RGB は何でも良いのだが、ここでは全て 0 とした。
三角形の部分では、描画すべき (アルファ値を非零とすべき) 幅と、その開始位置、さらには
RGB 値を計算する必要がある。幅は y が top に等しい場合に 1、bottom - 1 に等しい場合に
base で、y の増加とともに 1 ずつ増加する。開始位置は width からこの幅を引いて 2 で割れば
よい。スライダーの値から RGB の計算は、表示面を平面にとるか球面にとるかに依存する。ここ
では計算の単純な前者をとろう。G の値は y が top に等しい場合 base であり、bottom - 1 に
等しい場合に 1 である。本当は 0 としたいところであるが、単純さのために 1 としておき、G が
2 色立方体の断面投影図を考えれば当然なのだが、実は実装して最大強度にしても中央が白くならないのを見るまで勘
違いしていた。プログラミングによってモデルの誤りが明らかになるという端的な例であり、教育的配慮に加え自戒の意味
も込めてあえてこの様に書いてある。
5
protected void computeRow( int y, int[] row )
{
if ((0 <= y && y < top) || y > bottom) {
for (int i = 0; i < row.length; ++i) {
row[i] = 0;
}
}
else {
/* ... */
}
}
図 3: 三角形外の上下部分を計算するプログラム部分
0 となる場合はその外側で吸収することにしよう。三角形の左端で B の値は base、一方 R の値
は 0 であり、ピクセルごとに B は減少、R は増加である。この部分のコードは、従って、図 4 の
ようになろう。
protected void computeRow( int y, int[] row )
{
if ((0 <= y && y < top) || y > bottom) {
/* ... */
}
else {
int w = y - top + 1;
int start = (width - w) / 2;
int end = start + w;
int alpha = 0xFF << 24;
int g = bottom - y;
int i = 0;
for(; i < start && i < row.length; i++) {
row[i] = 0;
}
for(int r = w, b = 0; i < end && i < row.length; i++) {
row[i] = alpha | (r << 16) | (g << 8) | b;
r--;
b++;
}
for(; i < row.length; i++) {
row[i] = 0;
}
}
}
図 4: 三角形内部の ARGB を計算するプログラム部分
これで一応描画はできる筈である。しかしちょっと待ってほしい。そもそもの ColorChooserPanel
はマウスカーソルの位置 (x, y) にある RGB 値の取得を目的としたコンポーネントである。そし
てラスタ計算メソッド computeRow(int y, int[] row) は座標 (i, y) の ARGB 値を i を変化
させながら計算している。座標 (i, y) それぞれについて結局は ARGB 値を計算しているのであ
るから、computeRow(int y, int[] row) を座標 (i, y) の ARGB 値を計算する関数を呼び出
す様に書き換えておけば、マウスカーソルの位置から RGB 値を得るメソッドにも使えてメンテナ
6
ンスがしやすいだろう。その場合、コードは図 5 の様になろう。
public int getARGB(int x, int y)
{
int value = 0;
if (y >= top
int w = y
int start
int end =
&& y <= bottom) {
- top + 1;
= (width - w) / 2;
start + w;
if(x >= start && x <= end) {
int
int
int
int
alpha = 0xFF << 24;
g = bottom - y;
r = x - start;
b = w - r;
value = alpha | (r << 16) | (g << 8) | b;
}
}
return value;
}
protected void computeRow( int y, int[] row )
{
synchronized (this) {
for(int i = 0; i < row.length; i++) {
row[i] = getARGB(i, y);
}
}
}
図 5: 指定位置の ARGB を計算するプログラム部分
演習 2
クラス SRGBChooserPanel の内部クラスとして SRGBImage を実装せよ。但し SRGBChooserPanel
は javax.swing.colorchooser パッケージの一部とし、SRGBImage には以下のコンストラクタ及
びメソッドを実装すること。
• protected SRGBImage(int width, int height, Color color)
• protected SRGBImage(int width, int height, int intensity)
• public void setIntensity(Color color)
• public synchronized void setIntensity(int intensity)
ヒント: 与えられた Color の RGB 値の総計で intensity が求まるので、Color を引数とするメ
ソッドは RGB 値の総計を計算した上で intensity を引数とするメソッドを呼び出す様にすれば
よい。
7
4.2
4.2.1
SRGBChooserPanel の設計と実装
コンポーネントの配置
SRGBChooserPanel の核となる SRGBImage ができたところで、SRGBChooserPanel の設計と実装
を行なおう。それには AbstractColorChooserPanel を拡張しても、DefaultHSBColorChooserPanel
を拡張してもよい。見通しを良くするために、ここでは前者をとろう。
AbstractColorChooserPanel は抽象クラスであり、次のメソッドを実装する必要がある。
• public abstract void updateChooser()
• protected abstract void buildChooser()
• public abstract String getDisplayName()
• public abstract Icon getSmallDisplayIcon()
• public abstract Icon getLargeDisplayIcon()
アイコン化しないことにすれば、最後の二つのメソッドについては null を返せばよい。表示の際
の名前を返す getDisplayName() も、とりあえず何らかの文字列を返せばよい。従って検討すべ
きは最初の二つのメソッドである。
updateChooser() は、選択色の変更にともない呼び出されるメソッドであり、buildChooser()
は、ColorChooserPanel を構築するメソッドである。見通しの点から、まず後者を実装して、そ
れから前者を実装しよう。
最低限必要なコンポーネントは既に実装した SRGBImage を表示するためのラベル、スライダー、
RGB 値を表示するテキストボックスである。スライダーの横に 256 階調の濃淡バーなどあるとさ
らに素敵かもしれない。これらを個々に配置する方法もあるが、まとまりごとに JPanel に配置
し、その JPanel を SRGBColorChooserPanel に配置する方が処理しやすい。たとえば、図 6 の
ような具合である。
protected void updateChooser()
{
if(rgbImage == null)
return;
rgbLabel.setIcon(new ImageIcon(Toolkit.getDefaultToolkit().createImage(rgbImage)));
}
protected void buildChooser()
{
setLayout(new BorderLayout());
add(buildRGBPanel(), BorderLayout.CENTER);
add(buildSliderPanel(), BorderLayout.LINE_START);
}
図 6: buildChooser() の雛型
レイアウトマネジャを設定した後、二つのパネルを生成してそれぞれ SRGBChooserPanel に配置
している。レイアウトマネジャについては特に説明しないので、API ドキュメントを参照のこと。
buildRGBPanel() は RGB 値を表示する JTextField を生成し、パネルに配置の上そのパネルへ
8
の参照を返す図 7 の様な関数である。RGB それぞれについて同様の配置を行なうので、レイアウ
トマネジャとして標準的な GridBagLayout を用いているが、javax.swing.colorchooser パッ
ケージの SmartGridLayout を利用すればコードはより単純になる。JTextField と JLabel の生
成は RGB 各色について同じ操作が必要となるので、ひとまとめの関数とした。
proected JTextField redField, greenField, blueField;
proected JLabel redLabel, greenLabel, blueLabel;
protected JComponent buildRGBPanel()
{
Color color = getColorFromModel();
redField =
buildIneditableTextField(String.valueOf(color.getRed()),3);
/* greenField, blueField も同様 */
redLabel =
new JLabel(UIManager.getString("ColorChooser.hsbRedText"));
/* greenLabel, blueLabel も同様 */
GridBagLayout layout = new GridBagLayout();
JPanel panel = new JPanel(layout);
GridBagConstraints constraints = new GridBagConstraints();
addColorValueField(panel, redLabel, redField, layout, constraints);
/* green, blue も同様 */
return panel;
}
protected JTextField buildIneditableTextField(String value, int width)
{
JTextField field = new JTextField(value, width);
field.setEditable(false);
field.setHorizontalAlignment(JTextField.RIGHT);
field.setInheritsPopupMenu(true);
return field;
}
protected void addColorValueField(JPanel panel,
JLabel label,
JTextField field,
GridBagLayout layout,
GridBagConstraints constraints)
{
constraints.gridwidth = GridBagConstraints.RELATIVE;
layout.setConstraints(label, constraints);
panel.add(label);
constraints.gridwidth = GridBagConstraints.REMAINDER;
layout.setConstraints(field, constraints);
panel.add(field);
}
図 7: buildRGBPanel() の雛型
buildSliderPanel() はもう少し実際的である。基本的には JLabel、SRGBImage、JSlider の
インスタンスを生成してパネルに配置しているだけであるが、JLabel の生成の際には選択色
を囲む円を表示する様 paintComponent(Graphics) をオーバーライドしている。このコードは
9
DefaultHSBChooserPanel#createPaletteLabel() を参考しており、DefaultHSBChooserPanel
を継承するならそちらを使えばよい。コメントアウトされている buildSliderPanel() の二つの
関数呼び出しはコンポーネント間のメッセージパッシングに関するものであり、後程説明する。い
まはとりあえずコンポーネントを表示することだけを考えよう。図 8 に、コードを示す。
protected JComponent buildSliderPanel()
{
rgbLabel = buildImageLabel();
rgbImage = new SRGBImage(MAX_INTENSITY, MAX_INTENSITY,
getColorFromModel());
slider = new JSlider(JSlider.VERTICAL, MIN_INTENSITY,
MAX_INTENSITY*3, rgbImage.getIntensity());
// slider.addChangeListener(this);
// addMouseListeners();
JPanel panel = new JPanel();
panel.add(rgbLabel);
panel.add(slider);
return panel;
}
protected JLabel buildImageLabel()
{
return new JLabel() {
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Color color = g.getColor();
g.setColor(Color.white);
Point selected =
rgbImage.getXY(getColorFromModel());
g.drawOval(selected.x - radius, selected.y - radius, diameter, diameter);
g.setColor(color);
}
};
}
図 8: buildSliderPanel() の雛型
ここで、Point getXY(Color) はあたえられた Color に対応する画面上の位置を Point として
返すメソッドである。具体的には getARGB(int x, int y) の逆関数を SRGImage で実装すれば
よい。
肝腎の rgbImage はどこで使われているのだろうか? イメージをアイコン化して JLabel に配置
するのが図 9 にある updateChooser() の仕事である。
これで表示はできるはずである。念のため main(String[]) 関数を図 10 に示す。
演習 3
SRGBChooser で利用するコンポーネントが表示されるプログラムを作成せよ。実行時に -Xbootclasspath/a
オプションの指定が必要な場合もあるので注意すること。
10
protected void updateChooser()
{
if(rgbImage == null)
return;
rgbLabel.setIcon(new ImageIcon(Toolkit.getDefaultToolkit().createImage(rgbImage)));
}
図 9: updateChooser()
public static void main(String[] args)
{
JFrame frame = new JFrame();
JColorChooser cc =
new JColorChooser();
cc.addChooserPanel(new SRGBChooserPanel());
frame.add(cc);
frame.setVisible(true);
frame.pack();
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
}
図 10: テスト用 main 関数
4.2.2
他ペインとの相互作用
前節で必要なコンポーネントを画面に表示できる様にはなったが、スライダーを動かしても、
マウスを動かしても、他のペインで色を選択しても、何も起こらない。引き続きこれらの機能
を実装しよう。まずは他ペインとの相互作用からはじめよう。それには、updateChooser() で
rgbImage に色を指定すると同時に、RGB 各色の JTextField に値を指定してやればよい。変更
した updateChooser() は図 11 の様になろう。
変数 adjusting は updateChooser() の呼出中に再度 updateChooser() が呼び出されても問
題が起きない様にするための boolean である。コンストラクタで false にしておくこと。なお、
より適切な実装は adjusting を操作・参照するメソッドを synchronized とすることである。
4.2.3
スライダーへの対応
スライダーの動きに画像を対応させるためには、スライダーからの ChangeEvent を拾ってやればよ
い。それには SRGBChooserPanel で ChangeListener を実装し、slider ChangeListener として
登録してやればよい。ChangeListener を実装するに必要なのは public void stateChanged(ChangeEvent)
である。変更箇所だけ書き出すと図 12 の様になる。
clipY(int)、clipX(int, int) はカーソル位置をパレット図形内に納める関数である。clipY(int)
は与えられた y 座標がパレット図形の最大 y 値よりも大きければ最大 y 値を、最小 y 値よりも小さけ
れば最小 y 値を、それ以外の場合は与えられた y を、それぞれ返す関数である。clipX(int, int)
は与えられた x 座標について、最大 x 値、最小 x 値との間で同様の判断をする関数である。パ
11
protected void updateChooser()
{
if(rgbImage == null)
return;
if(!adjusting) {
adjusting = true;
Color color = getColorFromModel();
rgbImage.setColor(color);
rgbLabel.setIcon(new ImageIcon(Toolkit.getDefaultToolkit().createImage(rgbImage)));
setRGBValue(color);
adjusting = false;
}
}
protected void setRGBValue(Color color)
{
if(color.getAlpha() == 0) {
redField.setText("-");
greenField.setText("-");
blueField.setText("-");
}
else {
redField.setText(String.valueOf(color.getRed()));
greenField.setText(String.valueOf(color.getGreen()));
blueField.setText(String.valueOf(color.getBlue()));
if(!adjusting) {
adjusting = true;
getColorSelectionModel().setSelectedColor(color);
adjusting = false;
}
}
}
図 11: 他ペインでの色指定に対応した updateChooser()
レット図形のサイズは rgbImage を参照して決める必要があるので、SRGBImage に x 座標、y 座
標の最大値、最小値を返す関数を実装して、それを参照する様にすると良いだろう。
この他、buildSliderPanel() 中の slider.addChangeListener(this); の行のコメントを外
す必要がある。これで、スライダーを操作すると表示されるイメージが変わる筈である。
4.2.4
マウスによる色選択
マウスによる色選択には、マウスの移動やボタン押し下げに伴って発生する MouseeEvent を処
理してやればよい。インタフェースは MouseListener と MouseMotionListener であり、これら
を実装してやってもよいが、ChangeListener と違ってイベント処理メソッドが複数あるため、必
要な機能以外のメソッドについてもイベント処理コードを書かねばならない。その手間が省いて必
要な部分だけ書けば済む MouseAdapter と MouseMotionAdapter を利用しよう。マウスイベント
を扱う部分を書き出したものが図 13 である。
addMouseListeners() でマウスアダプタを生成し、redLabel についてイベントリスナとして登
録している。実際の処理はマウスボタン押し下げでも移動でも同じなので、setSelector(MouseEvent)
12
class SRGBChooserPanel
extends AbstractColorChooserPanel
implements ChangeListener
{
/* ... */
protected void updateChooser()
{
if(rgbImage == null)
return;
if(!adjsuting) {
adjusitng = true;
setColor(getColorFromModel());
adjusting = false;
}
}
public void setColor(Color color)
{
rgbImage.setColor(color);
refleshImage();
setRGBValue(color);
}
protected void refleshImage()
{
if(rgbImage == null)
return;
rgbLabel.setIcon(new ImageIcon(Toolkit.getDefaultToolkit().createImage(rgbImage)));
}
public void stateChanged(ChangeEvent e)
{
if (e.getSource() == slider) {
if(!adjusting) {
rgbImage.setIntensity(slider.getValue());
Point selected =
rgbImage.getXY(getColorFromModel());
int y = clipY((int)selected.y);
int x = clipX((int)selected.x, y);
Color color = rgbImage.getColor(x, y);
if(color.equals.getColorFromModel()) {
setRGBValue(x, y);
refleshImage();
}
else {
getColorSelectionModel().setSelectedColor(color);
}
}
}
}
/* ... */
}
図 12: スライダーの状態検出
にまとめてある。マウスの移動では rgbImage の更新が必要ないので、refleshImage() 呼出で
はなく rgbLabel を再描画するだけでよい。この他、buildChooser() でコメントアウトしていた
13
protected void addMouseListeners()
{
rgbLabel.addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e ) {
setSelector(e);
}
});
rgbLabel.addMouseMotionListener(new MouseMotionAdapter() {
public void mouseDragged(MouseEvent e){
setSelector(e);
}
});
}
protected void setSelector(MouseEvent e)
{
int y = e.getY();
int x = clipX(e.getX(), y);
setRGBValue(x, y);
rgbLabel.repaint();
}
図 13: マウスイベントの処理
addMouseListeners() 呼出しのコメントを外す必要がある。
演習 4
SRGBChooserPanel を、イベント処理を行なう様にせよ。また、DefaultHSBChooserPanel を
参考に、スライドバー横に無彩色のグラデーションを表示し、スライダーのトラックが表示されな
い様にしてみよ。
演習 5
SRGBChooserPanel に、適切にコメント文を追加し、javadoc で処理して API ドキュメントを
作成せよ。英語を用いることが望ましい。
演習 6
(応用) 画面サイズの制約から、マウス操作で扱えるスライダーの分解能には限りがある。低分
解能スライダと高分解能スライダを並立させて、マウスで全ての操作が可能であるようにせよ。二
つのスライダーは連動させること。更なる応用として、一方のスライダーを左右逆向きに描画させ
る (スライダーの指示方向が対向する様な形式にする) ことや、高分解能スライダに表示されてい
る部分を低分解能スライダに示す事が考えられる。後者については、三角パレットへの描画ポイン
ト標示が参考になろう。
14