Javascript チュートリアル 4:
JavaScript によるユーザインターフェイスの設計

イントロダクション

jsuiオブジェクトは、JavaScript を使って、Max 環境で使用するユーザインターフェイスオブジェクトを設計することを可能にしてくれます。jsuiオブジェクトのJavaScript の実装はjsオブジェクトと同様ですが、OpenGL コマンドによる 2D および 3D のベクタグラフィックス描画をサポートする API が追加されています。また、jsuiオブジェクトウィンドウ内でのマウスによるインタラクションを取り扱うメソッドも含まれています。

JavaScript によって提供される利点に加え、jsuiは UI の開発をフレキシブルに行うために、いくつかのビルトイン(組み込み)機能を提供します。

  • jsuiオブジェクトは、jsuiオブジェクトボックスのサイズに比例してその形状を描画します。jsuiオブジェクトのサイズ変更を行った場合、その中に描画される全ての要素は正しくサイズ変更されます。
  • jsuiオブジェクトは、ベクタグラフィックス言語(OpenGL)によって動作します。この言語は様々な単体のシェイプや描画プリミティブをサポートします。加えて、多くの高レベルグラフィックス関数が利用できます。jsuiオブジェクトはまた、画像のアンチエイリアシングを実行して可能な限り滑らかなオブジェクトを提供することができますが、この場合パフォーマンスは低下します。
  • jsuiオブジェクトは、オブジェクトボックスの境界を越えたシーンを描画することを可能にします。OpenGL 空間でカメラ位置を調整することによって、同じ UI オブジェクトの様々な「ビュー」(光景)を作り、管理することができます。jsuiオブジェクトは、ユーザインターフェイスの設計を行なうために使用できるだけでなく、単に Max パッチャー内でアルゴリズムによる描画操作を行うために組み込まれた OpenGL 描画エンジンとして使うことも可能です。

このチュートリアルでは、あなたがすでに他の JavaScript チュートリアルに目を通していることを前提としています。jsuiオブジェクトは、そのグラフィック言語のほとんどが OpenGL 関数に基づいていますが、その詳細はこのチュートリアルの範囲を超えています。OpenGL ‘Redbook’はこれらの関数の標準的なリファレンスです。オンラインバージョンは次の URL で手に入れることができます。

http://www.opengl.org/documentation/red_book_1.0/

訳注:2007/7/16 現在、次の URL でver1.1を入手できます。(上記 URL は無効になっています)
http://www.glprogramming.com/red/

また、次の URL で OpenGL 関係のドキュメント(英文)を見ることができます。
http://www.opengl.org/documentation/

日本語で読めるものとしては、新しい版(ver.2.0に対応)の訳が出版されています。

「OpenGL プログラミングガイド」ピアソンエデュケーション; 原著第5版
(2006/12/19)ISBN-10: 4894717239

jsuiでサポートされる OpenGL API は jsui sketchというオブジェクトに含まれています。このオブジェクトはほとんどの OpenGL コマンド、および記号定数を解釈します。OpenGLコード(例えば、’Redbook’にある C言語のサンプルコード)と、 jsui JavaScript コードの sketch メソッドやプロパティとの間の変換は、次のようなガイドラインを守ればほとんど直接行うことができます。

  • jsui sketch オブジェクトでは、すべての OpenGL コマンドは小文字で表します。例えば、glColor() は、sketch.glcolor() になります。

  • OpenGL の記号定数は、小文字で表すことに加え、’GL’というプリフィックスを取り除きます。そのため、例えば、GL_CLIP_PLANE1 は clip_plane1() になります。

多くの高レベル描画、およびシェイプコマンドが利用できます。これによって、ユーザインターフェイス開発がスピードアップするでしょう。これらのコマンドのリストを含む jsui sketch のリファレンスは、Javascript in Max マニュアル(および、jsuiオブジェクトのヘルプパッチ)にあります。

jsui の動作

チュートリアルパッチを開いて下さい。

チュートリアルパッチを見て下さい。グリーン色の背景に薄い赤の円が格子状に並んでいる jsuiオブジェクトがあることがわかると思います。オブジェクト内の円をクリックすると、円の色が濃い赤に変わります。同じ円をもう一度クリックすると、色は薄い赤に戻ります。

いくつかの円をクリックすると、その円は(濃い赤に)ハイライトします。パッチの右側の、router オブジェクトの上にある usliderオブジェクトを操作してみて下さい。どの円がクリックされている場合に、 router の下のどのusliderオブジェクトが上のスライダの値を反映しているか、その対応関係に注意して下さい。jsuiオブジェクトに接続された clearと書かれているメッセージボックスをクリックして下さい。円はすべて薄い赤に変わり、router オブジェクトは上の usliderオブジェクトからのメッセージを通さなくなります。0 0 1 、1 1 1、などのようなリストを含んだメッセージボックスをクリックして下さい。jsuiオブジェクトは更新され、斜めの列を表示します。router オブジェクトは上の1番目の usliderからのメッセージを下の1番目の usliderに渡すようになり、2番目以降に対しても同様に動作します。

この jsui オブジェクトは JavaScript コードを使って、Max の matrixctrl オブジェクトのいくつかの機能をエミュレートしています。横の列(columns) は router オブジェクトへの入力を表し、縦の行(rows)は出力を指定します。jsui は(入出力の状態を表すフォーマットの)リストを送ることによって router オブジェクトと通信しています。このリストは、router オブジェクトに対して、インレットで受け取ったメッセージを jsui オブジェクトのその時点の設定によって指定された適切なアウトレットへ送るよう命令するものです。

jsオブジェクトと同様、jsuiオブジェクトもJavaScript によって書かれたファイルからプログラムを読み込みます。このファイルは、サーチパス内のどこかに保存されているものです。jsuiはグラフィカルオブジェクトなので、ファイル名をタイプするオブジェクトボックスはありません。その代わり、jsuiオブジェクトのインスペクタを使って、JavaScript ソースファイルを指定します。

チュートリアルパッチをアンロックして、jsuiオブジェクトをハイライトさせてください。Object メニューから Get Info ... を選んで下さい。インスペクタが現れ、'JavaScript File' というテキストフィールドにjsuiソースファイルの名前 ('mymatrix.js') が表示されるはずです。

このインスペクタでは、オブジェクトのサイズや、オブジェクトの周囲のボーダーのオン、オフをセットすることもがきます。オブジェクトのボーダーを使用しない設定を行ない、Max パッチの背景色を jsuiオブジェクトの背景色と合わせることによって、シームレスなユーザインターフェイスの設計が可能になります。

描画コード

jsuiオブジェクトはグラフィカルなユーザインターフェイスオブジェクトです。そのため、オブジェクトをダブルクリックしても jsオブジェクトのようにテキストエディタは開きません。その代わりに、openと書かれたメッセージボックスをクリックしてください。JavaScript ファイル ('mymatrix.js') が書かれたテキストエディタが表示されます。このチュートリアルでは、JavaScript ファイルは、ディスク内のチュートリアルパッチと同じフォルダに保存されています。

jsのスクリプトと同様、この jsuiオブジェクトのためのコードも、グローバルブロックから始まっています。このブロックでは、オブジェクトのインレットとアウトレットやコードで使用するグローバル変数を定義することができます。また、オブジェクトが初期化される際に実行したいコマンドを書いておくこともできます。

// インレットとアウトレット inlets = 1; outlets = 1; // グローバル変数 var ncols=4; // デフォルトの列数 var nrows=4; // デフォルトの行数 var vbrgb = [0.8,1.,0.8,0.5]; var vmrgb = [0.9,0.5,0.5,0.75]; var vfrgb = [1.,0.,0.2,1.]; // ステート配列の初期化 var state = new Array(8); for(i=0;i<8;i++) { state[i] = new Array(8); for(j=0;j<64;j++) { state[i][j] = 0; } } // jsui のデフォルトを 2d にセット sketch.default2d(); // グラフィックスの初期化 draw(); refresh();

この JavaScript コードでは、2つのグローバル変数(ncols と nrowd) が定義され、すべての関数からアクセスできるようになっています。また、描画のための色の配列や、ユーザインターフェイスのどの円が「オン」でどの円が「オフ」かについての情報を保持するために使用する state 配列を作成するために、いくつかのグローバルなArray オブジェクトが定義されています。

JavaScript での多次元配列は、Arrayオブジェクトの各々の要素それ自身が Array オブジェクトであるような Arrayによってメモリ割り当てが行なわれます(2次元以上が必要な場合も同様です)。これは、多次元配列を直接宣言することができる他のプログラミング言語(例えば C 言語など)を経験した方には、何か不自然なもののように感じられるかも知れません。私たちのグローバルブロックにある for() ループ はこの割り当てを行い、配列 state の全ての要素を 0 に初期化するものです。一度、多次元配列が作られてしまえば、通常のブラケット(角括弧)による書法(例えば、state[4][2]というような書法)を使って参照することができます。

変数と配列の宣言に続いて、jsオブジェクトのグラフィック動作に関する3つのコマンドがあることがわかると思います。最初の、sketch.defalt2d() は、jsuiオブジェクトに対して、2次元シーンのグラフィックコマンドを送ることを前提としたいくつかのデフォルト動作を初期化するよう命じています。このコマンドは、OpenGLレンダリングコンテキスト上のデフォルトビューをセットし、いくつかのユーティリティルーチンを実行して、私たちがすぐにグラフィック要素をウィンドウに描画し始めることができるようにします。draw() コマンド(どのような名前をつけることもできます)は、メイングラフィック関数を参照するものです。このメイングラフィック関数は、jsuiオブジェクトのユーザインターフェイスを描くために必要なすべてのコマンドを含むように書きます。refresh() コマンドは、OpenGL のバックバッファ(フリッカー(ちらつき)を防ぐために最初に描画が行なわれる場所)を実際のスクリーンディスプレイにコピーします。refresh() コマンドをコメントアウトすると、jsuiオブジェクトが表示を行なおうとするのを妨げてしまいます。

グローバルブロックの下にある、draw() 関数を調べてみましょう。これは、jsuiに対して、スクリーンインターフェイスを描画するために必要な全てのコマンドを提供する関数です。

// draw -- メイングラフィック関数 function draw() { with (sketch) { // ポリゴンを描画する方法をセット // clear 色のセット glclearcolor(vbrgb[0],vbrgb[1],vbrgb[2],vbrgb[3]); glclear(); // 背景の消去 colstep=2./ncols; // column あたりどのくらい移動するか rowstep=2./nrows; // row あたりどのくらい移動するか for(i=0;i<ncols;i++) // columns を通した繰り返し { for(j=0;j<nrows;j++) // rows を通した繰り返し { // 描画ポイントの移動 moveto((i*colstep + colstep/2)-1.0, 1.0 - (j*rowstep +rowstep/2), 0.); if(state[i][j]) // 'on' の色の設定 { glcolor(vfrgb[0],vfrgb[1],vfrgb[2],vfrgb[3]); } else // 'off' の色の設定(vbrgb vfrgb の中間の色) { glcolor(vmrgb[0],vmrgb[1],vmrgb[2],vmrgb[3]); } circle(0.7/Math.max(nrows,ncols)); // 円の描画 } } } }

グラフィックスコマンド(すべて 'gl' で始まりますが、circle() コマンドと同様なものです)はすべて sketch オブジェクトのメソッド、およびプロパティです。これは、Math オブジェクトがほとんどの一般的な数学関数をカプセル化しているのと同じように、ほとんどの OpenGL APIをカプセル化したものです。

JavaScript の with() 文は、あるオブジェクト(この例では、OpenGL の機能を提供する sketch オブジェクト)に属するプロパティとメソッドを、すべてのコマンドにsketch という参照を行なわずに使えるようにするものです。with() を使わない場合、gcolor() の代わりに、sketch.gcolor()、circle() の代わりに sketch.circle() 等のように書かなければなりません。この便利な方法は、他のオブジェクト(例えば、Task や Patcher)に強く依存する関数でも使用することができます。

この draw() 関数は、いくつかのデフォルトの描画動作を設定し、配列 vbrgb で定義した色でウィンドウをクリアします。その後、オブジェクトのために定義した列の数(ncols)と行の数(nrows)に基づいて、配列 state を通した反復処理を行ないます。state の特定の要素が 0 (off) ならば、vmrgb で定義した色で円を描き、要素が 1 (on) の場合は、vfrgb の色で円を描きます。円の位置は行と列の数によって決定され、両軸が-1.0 と 1.0 の間でセットされた OpenGL ワールドの境界に基づいています(すなわち、jsuiウィンドウの中央は、OpenGL 座標では 0,0 になります)。

ワールドのサイズは -1.0 〜 1.0 の範囲の座標値に限定されるわけではありません。私たちのデフォルトビューポートは単に画面の中央に私たちの視点をセットするだけのものです。このビューポートは y の範囲は -1.0 〜 1.0、x の範囲はオブジェクトのアスペクト比に基づいてスケールされています。チュートリアルのオブジェクトボックスはたまたま正方形なので、範囲は両軸で同じになります。ビューポートを変更すると(例えば、仮想の「カメラ」の位置を操作すると)、jsui オブジェクトボックスの中で見える座標が変更されます。

訳注:チュートリアルパッチをアンロックして、jsui の縦のサイズを変えてみると、高さ(y軸)によって座標が拡大、縮小されるのがわかります。幅(x軸)を変更した場合、座標の拡大、縮小は行なわれず、見える範囲が広がります。

パラメータの設定

チュートリアルパッチで、ナンバーボックスの値を変更して行と列の数をセットして下さい。その値によって、オブジェクトが動的に円を 8 行 8 列まで追加する点に注目して下さい。JavaScript コードの rows() と cols() 関数を見て下さい。関数が自分自身の変数を設定した後、bang() 関数を呼び出している点に注意して下さい。

// rows -- jsui の行の数を変更 function rows(val) { if(arguments.length) { nrows=arguments[0]; bang(); // 描画を行い、ディスプレイを更新 } } // cols -- jsui の列の数を変更 function cols(val) { if(arguments.length) { ncols=arguments[0]; bang(); // 描画を行い、ディスプレイを更新 } }

 

オブジェクトに対するMax からの変更(マウスイベントを含みます)が行なわれるたびに、その直後に呼び出される bang() 関数は、単に、グローバルブロックで行なったように draw() と refresh() を呼び出しているだけです。これによって、jsuiオブジェクトを更新し、グラフィカルな変更をウィンドウに反映します。

// bang -- ディスプレイの描画とリフレッシュ function bang() { draw(); refresh(); }

必要に応じて描画を行なうことにより、オブジェクトによるプロセッサ時間の使用量を減らすことができます。

チュートリアルパッチで、jsuiオブジェクトに対する frgb および brgb メッセージのセットを行なう swatchオブジェクトを変更してみて下さい。JavaScript コードの中の対応する関数(frgb() および brgb())を見て下さい。'off' を表す円の色(vmrgb)のための配列が、frgb メッセージとbrgb メッセージによってセットされる色のちょうど中間になっていることに注意して下さい。

// frgb -- 円の描画色(及びクリックされた時の描画色)の変更 function frgb(r,g,b) { vfrgb[0] = r/255.; vfrgb[1] = g/255.; vfrgb[2] = b/255.; vmrgb[0] = 0.5*(vfrgb[0]+vbrgb[0]); vmrgb[1] = 0.5*(vfrgb[1]+vbrgb[1]); vmrgb[2] = 0.5*(vfrgb[2]+vbrgb[2]); bang(); // 描画とディスプレイの更新 } // brgb -- 背景色の変更 function brgb(r,g,b) { vbrgb[0] = r/255.; vbrgb[1] = g/255.; vbrgb[2] = b/255.; vmrgb[0] = 0.5*(vfrgb[0]+vbrgb[0]); vmrgb[1] = 0.5*(vfrgb[1]+vbrgb[1]); vmrgb[2] = 0.5*(vfrgb[2]+vbrgb[2]); bang(); // 描画とディスプレイの更新 }

OpenGL における色は、0.0 〜 1.0 の範囲の4つの浮動小数点数の値として表現され、それぞれ赤、(red)、緑(green)、青(blue)、アルファ(alpha-透明度)の量に対応しています。これは、通常、整数 0 〜 255(アルファの値を持ちません)によって色を記述する多くのビデオシステムと対照的です。私たちの frgb() および brgb() 関数のほとんどの動作では、後者(swatch オブジェクトで使われるもの)を前者(jsuiオブジェクトが理解するもの)に変換しています。

マウスによるインタラクション

チュートリアルパッチをアンロックし、jsuiオブジェクトのサイズを変更して下さい(円のサイズが動的に変更されます)。パッチャーをロックして、マウスクリックによって、依然として正しい円の状態の変更が行なわれることに注目してください。再びパッチをロックして、JavaScript コードの onclick() 関数 を見て下さい。

onclick()、ondblclick()、ondrag() 関数が定義されている場合、jsuiオブジェクトに対して、ユーザがオブジェクト上でマウスのクリック、ダブルクリック、ドラッグを行なったときにどうすれば良いかが命じられます。この関数は、アクションが起きたオブジェクトウィンドウ内の場所、およびいくつかのフラグ(マウスボタンが押されていたかどうか、[shift] キーの状態など)をアーギュメントとして呼び出されます。このチュートリアルの onclick 関数では、最初の2つのアーギュメントだけを使っています。これは、マウスクリックが行なわれた場所のx および y座標に相当するものです。

// onclick -- マウスクリックイベントへの対応 function onclick(x,y) { worldx = sketch.screentoworld(x,y)[0]; worldy = sketch.screentoworld(x,y)[1]; colwidth = 2./ncols; // ワールド座標による列の幅 rowheight = 2./nrows; // ワールド座標による行の高さ x_click = Math.floor((worldx+1.)/colwidth); // どの列をクリックしたか y_click = Math.floor((1.-worldy)/rowheight); // どの行をクリックしたか // クリックされた場所のステートを反転 state[x_click][y_click] = !state[x_click][y_click]; // クリックされた点の座標位置と// ステートを出力 outlet(0, x_click, y_click, state[x_click][y_click]); bang(); // 描画とディスプレイの更新 s}

OpenGL グラフィックスワールドは浮動小数点数による座標(このケースでは、-1.0と1.0)で定義されています。jsuiマウス関数は、マウスイベントが発生した位置を、ピクセル(オブジェクトボックスの左上隅から数えます)による座標で返します。格子状に並んでいる円に対してマウスイベントを正しく評価するためには、これらの2つのシステム(ワールド座標とスクリーン座標)の間で値の変換を行う必要があります。sketch のメソッド worldtoscreen() と screentoworld() はこのような変換を行ってくれます。

worldx = sketch.screentoworld(x,y)[0]; worldy = sketch.screentoworld(x,y)[1];

クリックした場所の幅と高さがわかったならば、x軸方向とy軸方向の円の数によって座標全体の幅を割り、行と列の幅を得ます。

訳注:この次の式は個々の行、及び列の幅を求めています。「クリックした場所」ではなく「全体の」と考えた方が意味がわかりやすいでしょう。

colwidth = 2./ncols; // ワールド座標による列の幅 rowheight = 2./nrows; // ワールド座標による行の高さ

そして、マウスクリックが行われた座標を適用することによって、クリックされた位置から一番近い円を求めることができます。

x_click = Math.floor((worldx+1.)/colwidth); // どの列をクリックしたか y_click = Math.floor((1.-worldy)/rowheight); // どの行をクリックしたか

クリックした円が求められたら、配列 stateのそれに対応した要素を反転させます。

state[x_click][y_click] = !state[x_click][y_click];

配列 state を正しくセットした後、変更が行われた箇所に対応するリストを、jsuiオブジェクトのアウトレットから Max へ送信します。そして、jsuiオブジェクト自身に対して bangを送り、変更を反映するようにグラフィックスを更新します。

outlet(0, x_click, y_click, state[x_click][y_click]); bang();

onclick() 関数をローカルに設定している点に注意して下さい。これにより、Max パッチからの onclick メッセージでトリガすることができないようになっています。

JavaScript コード、および、それがパッチャーの中でのjsuiオブジェクトの動作に関わる方法に慣れ親しんで下さい。パッチの右側にある metroオブジェクトをアクティブにするために、toggleオブジェクトをクリックして下さい。これは、usliderオブジェクトからの入力をシミュレートするものです。マウスクリックによって値が渡され、リストとして出力されるまでの流れを確認しやすくするために、JavaScript コードに post()文を置いてみて下さい。

結び

jsuiオブジェクトは、JavaScript をプログラミング言語として使用し、カスタマイズ可能なユーザ・インターフェイスの設計、および実装を可能にしてくれる、非常にパワフルなツールです。プログラムのキーポイントは、メイン描画関数(jsuiオブジェクトのグラフィカルな表示を行うための一連のコマンドを定義します)および、マウスインタラクション関数である、onclick()、ondblclick()、ondrag() が必要であるということです。jsuiの sketch オブジェクトで使われる OpenGL API と、Max環境の間には、色の表現の違い(OpenGL では浮動小数点数、Max では整数)、空間座標表現の違い(OpenGLでは浮動小数点数によるワールド座標、Maxではピクセルによるデカルト座標)があるということは、ぜひ覚えておきたい重要な点です。

コードリスト

// mymatrix.js // // matrixctrl のような、クリックで操作できる簡単な格子状スイッチのシミュレーション // // rld, 5.04 // // インレットとアウトレット inlets = 1; outlets = 1; // グローバル変数 var ncols=4; // デフォルトの列数 var nrows=4; // デフォルトの行数 var vbrgb = [0.8,1.,0.8,0.5]; var vmrgb = [0.9,0.5,0.5,0.75]; var vfrgb = [1.,0.,0.2,1.]; // ステート配列の初期化 var state = new Array(8); for(i=0;i<8;i++) { state[i] = new Array(8); for(j=0;j<64;j++) { state[i][j] = 0; } } // jsui のデフォルトを 2d にセット sketch.default2d(); // グラフィックスの初期化 draw(); refresh(); // draw -- メイングラフィック関数 function draw() { with (sketch) { // ポリゴンを描画する方法をセット // clear 色のセット glclearcolor(vbrgb[0],vbrgb[1],vbrgb[2],vbrgb[3]); glclear(); // 背景の消去 colstep=2./ncols; // column あたりどのくらい移動するか rowstep=2./nrows; // row あたりどのくらい移動するか for(i=0;i<ncols;i++) // columns を通した繰り返し { for(j=0;j<nrows;j++) // rows を通した繰り返し { // 描画ポイントの移動 moveto((i*colstep + colstep/2)-1.0, 1.0 - (j*rowstep +rowstep/2), 0.); if(state[i][j]) // 'on' の色の設定 { glcolor(vfrgb[0],vfrgb[1],vfrgb[2],vfrgb[3]); } else // 'off' の色の設定(vbrgb vfrgb の中間の色) { glcolor(vmrgb[0],vmrgb[1],vmrgb[2],vmrgb[3]); } circle(0.7/Math.max(nrows,ncols)); // 円の描画 } } } } // bang -- ディスプレイの描画とリフレッシュ function bang() { draw(); refresh(); } // rows -- jsui の行の数を変更 function rows(val) { if(arguments.length) { nrows=arguments[0]; bang(); // 描画を行い、ディスプレイを更新 } } // cols -- jsui の列の数を変更 function cols(val) { if(arguments.length) { ncols=arguments[0]; bang(); // 描画を行い、ディスプレイを更新 } } // list -- Max からの変更に応答して内部のステートを更新 function list(v) { if(arguments.length==3) // アーギュメントの数が正しくない場合は何もしない { // リストに基づいて内部のステートを更新 state[arguments[0]][arguments[1]]=arguments[2]; // アウトレットからリストをエコー出力 outlet(0, arguments[0], arguments[1], arguments[2]); } bang(); // ディスプレイの描画と更新 } // clear -- ステートを再初期化 function clear() { for(i=0;i<ncols;i++) { for(j=0;j<nrows;j++) { state[i][j]=0; // ステートを再初期化 } } outlet(0, “clear”); // router または matrix~ のダウンストリームをクリア bang(); // ディスプレイの描画と更新 }th = 2./ncols; // ワールド座標による列の幅 rowheight = 2./nrows; // ワールド座標による行の高さ x_click = Math.floor((worldx+1.)/colwidth); // どの列をクリックしたか y_click = Math.floor((1.-worldy)/rowheight); // どの行をクリックしたか // クリックされた場所のステートを反転 state[x_click][y_click] = !state[x_click][y_click]; // クリックされた点の座標位置と// ステートを出力 outlet(0, x_click, y_click, state[x_click][y_click]); bang(); // 描画とディスプレイの更新 } onclick.local = 1; // Max からのトリガを防ぐために関数をプライベートにします。 // ondblclick -- onclick()に処理を渡す function ondblclick(x,y) { onclick(x,y); } ondblclick.local = 1; // Max からのトリガを防ぐために関数をプライベートにします。

参照

jsui JavaScript UI オブジェクト
swatch RGB カラーを選択し、表示するために使用するカラースウォッチ(色見本)