Chapter 12:
グラフィックスウィンドウ

注:これらのルーチンは、Windows XP 上ではサポートされません。また、Max OSX 上でも、近い将来いくつかの点で時代遅れのものとなってしますかもしれません。私たちは、シンプルなグラフィックスを扱う場合に、これらのルーチンを使ったオブジェクトを実装するより、lcd オブジェクトを使うことを推奨します。より高度なレベルのグラフィックス操作を行う場合には、Jitter環境で使用するエクスターナルオブジェクトを開発することを推奨します。

Max にはビットマップグラフィックスや、アニメーションやペイントプログラムでのインタラクションに必要なオフスクリーン「スプライト」を管理するための、ルーチンのセットがあります。Max ユーザによって、graphic オブジェクトで GWind と呼ばれるグラフィックスウィンドウが作られます。しかし、このウィンドウは、エクスターナルオブジェクトでの gwind_new の呼び出しによって作ることも可能です。グラフィックスウィンドウは、主として表示のための媒体となります。特別なユーザインターフェイスを作りたい場合には、独自のウィンドウを作る方がよいでしょう( Chapter 10 を参照して下さい)。Max プログラマは、MouseState オブジェクトを使ってグラフィックスウィンドウ内のマウスにアクセスすることができます。しかし、グラフィックスウィンドウを使うオブジェクトでは、描画(アニメーション、シェイプ、テキストなど)だけを行なう場合のほうが一般的です。t_gwind 構造体、および関連した定数は、ext_anim.h で定義されています。

グラフィックスウィンドウ・ルーチン

ここで紹介するのは、グラフィックウィンドウを作ったり、それらをシンボルと関係づけたり、その中に何かを描画するためのルーチンです。

colorinfo

     
  カレントのカラー環境に関する情報を取得するために、colorinfo を使います。
   
  void colorinfo (CInfoRec *cinfo);
     
  cinfo カラー環境に関する情報を持つCInfoRec データ構造体。後述の、CInfoRec の定義を参照して下さい。
     
 

colorinfo は、既存の CInfoRec (ext_anim.hで定義)の中のフィールドを満たします。この構造体は次のように定義されています。

typedef struct { short c_hasColorQD; short c_depth; short c_has32bitQD; short c_inColor; short c_curDevH; short c_curDevV; } CInfoRec;

c_hasColorQD はマシンが ROM に Color QuickDraw を持っている場合は、0以外の値になります。 c_depth はスクリーンのビット深度(1-24)です。c_has32bitQD はシステムに32ビットの QuickDraw がインストールされている場合に、0以外の値になります。c_inColor はカラー環境の場合に、0以外の値になり、モニタがグレースケール表示にセットされている場合には0になります。c_curDevHc_curDevV はそれぞれ、カレントのGDevice の幅と高さです。

一般的には、初期化ルーチンで colorinfo を呼出し、その結果をグローバル変数に保存します。c_depthc_inColor は時間につれて変わることができる点に注意して下さい。これらの情報が重要である場合には、 colorinfo をより定期的に呼び出すことを考慮して下さい。ユーザインターフェイスオブジェクトを書いている場合、box_usercolor 関数を使って、オブジェクトの描画をカラーで行うかどうかを判断することができます。


gwind_new

     
  新規にグラフィックウィンドウ・オブジェクトを作る場合、gwind_new を使います。
   
 

t_gwind *gwind_new (t_object *assoc, t_symbol *name, short flags,
short left, short top, short right, short bottom);

     
  assoc あなたのオブジェクトへのポインタ
  name GWind にアクセスすることができる他のオブジェクトの名前。Gwind へのアクセスは、すべてシンボルへのバインディングを通して行われます。これは、Max の table オブジェクトがシンボルにバインドされる場合と同様なものです。
  flags 1 :タイトルバーなしで作られたウィンドウが欲しい場合, 0 :それ以外の場合
  left ウィンドウの左端のグローバル座標.
  top ウィンドウの上端のグローバル座標.
  right ウィンドウの右端のグローバル座標.
  bottom ウィンドウの下端のグローバル座標.
     
 

qwind_new は、新しい GWind オブジェクトを生成します。wind_new と違って、あなたが作成したグラフィックスウィンドウは直ちに見ることができます。


gwind_offscreen

     
  グラフィックスウィンドウのオフスクリーンバッファを初期化するために、gwind_offscreen を使います。
   
  void gwind_offscreen (t_gwind *gw);
     
  gw グラフィックスウィンドウ.
     
 

GWind に offscreen メッセージを送ることによって、この関数を実行することもできます。


gwind_get

     
  GWind が結びつけられているシンボル名を取得するために、gwind_get を使います。
   
  t_gwind *gwind_get (t_symbol *name);
     
  name グラフィックスウィンドウに結びつけられた名前
     
 

Gwind オブジェクトが シンボル name に結びつけられている場合、gwind_get はそれへのポインタを返します。そうでない場合、0を返します。あなたのメソッドが GWind へのアクセスを開始しようとする場合ごとに毎回、gwind_get を呼び出さなければなりません。これは、GWind オブジェクトが存在し続けているかどうかについて、知ることができないからです。典型的な gwind_get の使用法は、gwind_setport のサンプル内にあるので、参照して下さい。


gwind_setport

     
  カレントグラフポートをグラフィックスウィンドウにセットする場合に、gwind_setport を使います。
   
  t_gwind *gwind_setport (t_gwind *gw);
     
  gw グラフィックスウィンドウ
     
 

GWind gw が可視である場合、gwind_setport は それに結びつけられている Macintosh ウィンドウに対して SetPort を実行し、それまでのグラフポートを返します。ウィンドウが不可視の場合には、gwind_setport は 0を返します。

下記の例のように、すべてのグラフィックスウィンドウへの描画は、gwind_setport によって開始されなければなりません。また、正しい gwind_get の使い方にも注意して下さい。ここでは、myObject の m_windsym フィールドが(おそらく)グラフィックスウィンドウオブジェクトの名前を保持しているシンボルであると仮定しています。

void myobject_draw(myObject *x) { GrafPtr save; t_gwind *g; if (g = gwind_get(x->m_windsym)) { /* 存在するか? */ if (save = gwind_setport(g)) { /* 可視であるか? */ /* ここで GWind の中に描画を行ないます。*/ SetPort(save); } } }


オフスクリーン・ルーチン

GWind はオフスクリーンやスプライトルーチンを使うように設計されていますが、これらをあなた自身のウィンドウで使ってみようと思うのであれば、それも不可能なわけではありません。上記のように、gwind_offscreen は、Gwind のOffscreen 構造体を初期化します。off_new によってあなた自身のウィンドウの Offscreen 構造体を初期化することができます。

オフスクリーン・ルーチンは、不快な画面のフリッカ(ちらつき)を最小限に抑えるために「バッファリング」による描画の処理を行います。この機能は32ビットQuickdraw GWorld 関数と同じ機能を提供します。そして、Offscreen 構造体は、利用できる32ビット QuickDraw を透過的に使用します。ルーチンのユーザは、GWorld が使われているかどうかについて心配する必要はありません。一般的に、Offscreen 構造体の中に描画を行なう場合、スプライト・ルーチンを使用します。Offscreen データ構造体は、ext_anim.h で宣言されています。

off_new

     
  新しい Offscreen 構造体を作る場合に、off_new を使います。
   
  Offscreen *off_new (GrafPtr dest);
     
  dest Offscreen が書き込まれるウィンドウ
     
 

off_new は、リンクされているグラフポート dest と同じサイズの、新しい Offscreen 構造体を作ります。あなたのウィンドウのサイズが変更される場合には、off_resize を呼び出します。スプライトを使用する前に、オフスクリーンを作らなければなりません。オフスクリーンは Max オブジェクトではないため、off_free によって解放します。


off_free

     
  オフスクリーンを破棄する場合に、off_free を使います。
   
  void off_free (Offscreen *os);
     
  os 解放したいOffscreen 構造体

off_copy

     
  オフスクリーン全体を、関連づけられたグラフポートにコピーする場合に、off_copy を使います。
   
  void off_copy (Offscreen *os);
     
  os コピーされる Offscreen 構造体

off_copyrect

     
  オフスクリーンの一部を、関連づけられたグラフポートにコピーする場合に、off_copyrect を使います。
   
  void off_copyrect (Offscreen *os, Rect *src);
     
  os コピーされる Offscreen 構造体
  src コピーする矩形領域
     
 

オフスクリーンの中の矩形領域 src は、オフスクリーンのコピー先(デスティネーション)グラフポートの同じ場所にコピーされます。


off_maxrect

     
  オフスクリーン内のソースとなる2つの矩形領域をカバーするような矩形領域を取得するために、 off_maxrect を使います。
   
  void off_maxrect (Offscreen *os, Rect *src1, Rect *src2, Rect *result);
     
  os Offscreen 構造体.
  src1 カバーされる矩形領域
  src2 カバーされるもう1つの矩形領域
  result src1 と src2 の矩形領域を含み、os の境界内に収まるような矩形領域がここに返されます。

off_tooff

     
  BitMap(ビットマップ) をオフスクリーンにコピーする場合に、off_tooff を使います。
   
  void off_tooff (Offscreen *os, PixMapHandle *src, Rect *srcRect, Rect *dstRect);
     
  os Offscreen 構造体
  src コピーされるビットを含む PixMap または BitMap
  srcRect コピーしたい src の部分
  dstRect ビットをコピーするオフスクリーンの中の位置
     
 

この関数は、src の中のピクセルを、スクリーンへはコピーせず、オフスクリーンバッファへコピーします。 src は BitMap へのポインタでもあり得ます。


off_resize

     
  オフスクリーンのサイズを変更して、関連づけられたグラフポートのサイズと合致させる場合に、off_resize を使います。
   
 

void off_resize (Offscreen *os);

     
  os Offscreen 構造体
     
 

ユーザがオフスクリーンを持つウィンドウのサイズ変更を行った場合に、このルーチンを呼び出します。


スプライト・ルーチン

スプライトは、設定した矩形領域の中にイメージを描画する、独立したエンティティ(実体)です。スプライトシステムは、オフスクリーンオブジェクトによって所有されます。そのため、例えば Max のアクティブなグラフィックスウィンドウの1つ1つに対して、異なったスプライトのセットが存在します。個々のスプライトはプライオリティ(優先度)を持っていて、前景から背景までのオブジェクトをレイヤーにする(層にして重ねる)ために使われます。スプライトは動的にプライオリティを変えることができます。スプライトの数や、異なるプライオリティのレベル数に対するセット内での制限はありません。同じプライオリティレベルの2つ以上のスプライトがある場合、最初にオフスクリーン構造体に結びつけられたものが、より後から結びつけられたものの前に描画されます。

スプライト構造体は、ext_anim.h で定義されています。スプライトオブジェクトのフィールドを知らなくても何とかなるでしょうが、(スプライトの矩形領域などについて知っていることは)役に立つ場合があることは間違いありません。

typedef struct sprt { struct object s_ob; GrafPtrs_dest; /* デスティネーション(転送先)のスクリーン */ Rect s_rect; /* 長方形領域 */ BitMapHandle s_mask; /* マスク */ RgnHandle s_rgn; /* マスク領域 */ int s_number; /* スプライトナンバ (プライオリティ) */ char s_drawn; /* is it drawn */ char s_change; /* message to sprite proc to go to "next" frame */ void *(*s_proc)(); /* 描画するプロシージャ */ long s_frame; /* 現在のフレーム, s_proc によって使用される */ long s_misc; /* s_proc によって使用される */

void *s_assoc; /* 関連づけられているオブジェクト */ OffScreen *s_owner; /* owning system */ struct sprt *s_prev; /* リンク */ struct sprt *s_next; /* リンク */ } Sprite;

次に述べる全てのスプライト描画ルーチン(sprite_movesprite_rect など)は、スプライトの転送先のグラフポートの所有者が可視である場合、自動的に他のスプライトをスクリーンに再描画します。

sprite_new

     
  新しいスプライトを作る場合に、sprite_new を使います。
   
  Sprite *sprite_new (t_object *assoc, Offscreen *owner,
long priority, Rect *frame, ProcPtr *drawProc);
     
  assoc あなたのオブジェクトへのポインタ
  owner 関連づけられた Offscreen 構造体。ここにスプライトが描画されます。
  priority スプライトのプライオリティナンバ。0はバックグラウンド、数値が大きいほど前面になります。
  frame スプライトが描画される長方形領域
  drawProc 現在の状態に基づいてスプライトを描画する関数。宣言の方法は下記を参照して下さい。
     
 

この関数は、新しいスプライトオブジェクトを作ります。これは所有者のオフスクリーン環境に描画されます。描画プロシージャ drawProc は次のように宣言されなければなりません。

void myObject_spritedraw (myObject *obj, Sprite *spr);
   
obj あなたのオブジェクト
spr 描画するスプライト.

このルーチンでは、標準の Quickdraw 呼出し(PaintRectなど)を行うことができ、イメージはオフスクリーンに記録されます。そして、全てのスプライトのレイヤ処理が保証されるための適当な時間の後、関連づけられたグラフポートにコピーされます。


sprite_move

     
  スプライトの位置を相対的に変更する場合に、spreite_move を使います。
   
  void sprite_move (Sprite *spr, short deltaH, short deltaV);
     
  spr 移動させるスプライト
  deltaH ピクセル単位による、スプライトを移動させる水平方向の距離
  deltaV ピクセル単位による、スプライトを移動させる垂直方向の距離
     
 

この関数は、スプライトの長方形領域を水平方向に deltaH ピクセル、垂直方向に deltaV ピクセル移動させます。移動前の位置にあるスプライトは消去され、新しい場所に再描画されます。移動によって影響を受ける他のスプライトも再描画されます。スプライトを、同じ場所で、異なるアピアランスで再描画したい場合には次のように使用します。

sprite_move(mySprite,0,0)


sprite_moveto

     
  指定した位置にスプライトを移動させる場合、sprite_moveto を使います。
   
  void sprite_move (Sprite *spr, short h, short v);
     
  spr 移動させるスプライト.
  h スプライトの長方形領域の新しい左端の座標
  v スプライトの長方形領域の新しい上端の座標
     
 

この関数は、スプライトの長方形領域を、指定した位置に移動させます。移動前の位置にあるスプライトは消去され、新しい場所に再描画されます。移動によって影響を受ける他のスプライトも再描画されます。


sprite_rect

     
  スプライトの矩形領域を変更する場合に、sprite_rect を使います。
   
  void sprite_rect (Sprite *spr, Rect *newRect, short change, short next);
     
  spr 長方形領域を変更するスプライト
  newRect 新しい矩形領域
  change スプライトの s_change フィールドに保存される値。必要なら、スプライトの描画プロシージャによって、どのようにでも解釈することができます
  next スプライトの s_next フィールドに保存される値。必要なら、スプライトの描画プロシージャによって、どのようにでも解釈することができます
     
 

この関数はスプライトの矩形領域を newRect に変更し、再描画します。


sprite_redraw

     
  スプライトを再描画する場合に、sprite_redraw を使います。
   
  void sprite_redraw (Sprite *spr, short deltaH, short deltaV, short change, short next);
     
  spr 再描画するスプライト
  deltaH ピクセル単位による、スプライトを移動させる水平方向の距離
  deltaV ピクセル単位による、スプライトを移動させる垂直方向の距離
  change スプライトの s_change フィールドに保存される値。必要なら、スプライトの描画プロシージャによって、どのようにでも解釈することができます.
  next スプライトの s_next フィールドに保存される値。必要なら、スプライトの描画プロシージャによって、どのようにでも解釈することができます
     
 

sprite_redrawo は、sprite_move に似ていますが、スプライトの change および next のフィールドををセットすることもできます。


sprite_erase

     
  スプライトを消去する場合に、sprite_erase を使います。
   
  void sprite_erase (Sprite *spr);
     
  spr The Sprite to erase.
     
 

この関数は、スプライトを消去し、その背後に隠れていた他のスプライトがあれば、それを描画します。


sprite_newpriority

     
  スプライトのプライオリティを変更する場合に、sprite_newpriority を使います。
   
  void sprite_newpriority (Sprite *spr, long priority);
     
  spr スプライト
  priority スプライトの新しいプライオリティ。 0はバックグラウンド、数値が大きくなるに従って、前面になります。
     
 

この関数はスプライトに新しいプライオリティを割り当て、プライオリティの変更に伴って再描画する必要があるスプライトを所有する Offscreen 構造体のすべての要素を再描画します。


スプライトの例

次の例では、オブジェクトは、スプライトを使って楕円形を描く、keyメソッドを持っています。ここでは、Qelem の中で描画に使われるデータ構造体を int メソッドによるそれらの変更から切り離す等の、役に立つテクニックをいくつか紹介しています。

このオブジェクトは4つのインレットを持ち、それぞれは楕円形の長方形領域の座標を受け取ります。データ構造体は次のように定義されます。

typedef struct oval { struct object o_ob; long o_priority; /* スプライトのプライオリティ */ Sprite *o_sprite; /* スプライト */ Rect o_bounds;/* 場所がどこか? */ Rect o_dbounds; /* どこに描画するか? */ void *o_qelem; /* Qelem */ t_symbol *o_sym; /* GWind のシンボル */ } Oval;

これは、初期化ルーチンです。

void main(void *p) { setup((t_messlist **)&OvalClass, oval_new, oval_free, (short)sizeof(Oval), 0L, A_SYM, A_LONG, A_DEFLONG, A_DEFLONG, A_DEFLONG, A_DEFLONG, 0); addint(oval_int); addinx(oval_in1,1); addinx(oval_in2,2); finder_addclass("Graphics","oval"); }

これは、オブジェクトのインスタンス生成関数です。queue 関数 oval_qfn の中では、スプライトは必要となるまで作られていない点に注意して下さい。私たちは常に、描画を行なうたびににシンボルを通して GWind を参照します。これは、描画をしようとする時点で、GWind が確かに存在しているという保証がないためです。

void *oval_new(t_symbol *windName, long priority, long left, long top, long bottom, long right) { Oval *x; x = (Oval *)newobject(OvalClass); intin(x,3); intin(x,2); intin(x,1); SetRect(&x->o_bounds, (short)left, (short)top, (short)bottom, (short)right); x->o_dbounds = x->o_bounds; x->o_sym = windName; x->o_priority = priority; x->o_qelem = qelem_new(x,oval_qfn); x->o_sprite = 0; return (x); }

これは、int メソッドです。これらは、o_bounds 矩形領域の座標をセットし、一番左の値は、楕円形を描画するために Qelem をセットします。あなたのメソッドは割り込みレベルで実行されるため、intbang メッセージに応答して、直接スクリーンに描画することはできません。

void oval_bang(Oval *x) { qelem_set(x->o_qelem); } void oval_int(Oval *x, long left) { x->o_bounds.left = left; oval_bang(x); } void oval_in1(Oval *x, long top) { x->o_bounds.top = top; } void oval_in2(Oval *x, long right) { x->o_bounds.right = right; } void oval_in3(Oval *x, long bottom) { x->o_bounds.bottom = bottom; }

queue 関数です。ここで全ての動作が行われます。

void oval_qfn(Oval *x) { GrafPtr gp; t_gwind *it; x->o_dbounds = x->o_bounds; /* make a copy of the new rect */ it = gwind_get(x->o_sym); if (!it || !(gp = gwind_setport(it))) { /* doesn’t exist or not visible? */ return; } if (it->g_off) { /* if there’s an Offscreen */ if (!x->o_sprite) /* need to make a new Sprite? */ x->o_sprite = sprite_new(x,it->g_off,x->o_priority, &x->o_dbounds,oval_spritedraw); sprite_rect(x->o_sprite,&x->o_dbounds,0,0); /* draw */ } SetPort(gp); }

これは、スプライトの drawProc です。描画するための境界をスプライトの矩形領域 s_rect から取得している点に注意して下さい。これは、不可欠ではありませんが、スプライトを描きたい場所に常に描画を実行することを保証します。

void oval_spritedraw(Oval *x, Sprite *s) { EnterCallback(); x->o_dbounds = s->s_rect; PaintOval(&x->o_dbounds); ExitCallback(); }

最後に、これはオブジェクトの free 関数です。

void oval_free(Oval *x) { EnterCallback(); qelem_free(x->o_qelem); if (x->o_sprite) freeobject(x->o_sprite); ExitCallback(); }

この例では、Gwind の中で、スプライトによって描画を行う方法について示しました。実際の Max の oval オブジェクトはこれより少々複雑になっています(oval オブジェクトは様々な形や色で描画することができます)。しかし、oval オブジェクトが使っているスプライトのテクニックは、このサンプルで示した例と同じものです。