タイトル小池邦人のプログラミング日記》2001/8/13<Quartz 2D(Core Graphics)を使う その2>カテゴリーグラフィックス, 小池邦人のプログラミング日記
作成日2001/8/13 22:29:5作成者新居雅行
今回は、自作CarbonアプリケーションにQuartz 2Dで描画可能な環境を用意してみます。加えて、Quartz 2DとQuickDraw描画環境との融合についても考えてみたいと思います。

先週、めでたくAppleのSDKサイトにCarebonLib 1.4 SDK GMが登録されました。多分、このスペックのCarbonLib FrameworkがMac OS X 10.1にも搭載されるのでしょう。これにより、CarebonLibの開発も一段落でしょうか?Documentationフォルダにある「CarbonLib 1.4 TN.pdf」の「Changes from CarbonLib 1.3.1 to 1.4」の章を読んでみると、幾つか新しいAPIが追加されたことが確認できます。その中でも興味深いのは、SetWindowGroup()やGetWindowGroup()などのウィンドウグループに関係するAPIの存在です。ウィンドウグループとは、複数ウィンドウをひとつのグループとしてまとめて取り扱うための仕組みです。例えば、グループ内のどれかひとつのウィンドウを移動させると、同時にグループ内すべてのウィンドウを移動することができます。先んじて、Sheet Windowなどで採用されていた機能が、デベロッパー側にも解放されたわけです。また、Mac OS 8.6や9.04でも「Data-Fork-Based Resource」がサポートされました。今までは、Mac OS 9.1でしか利用できなかったようです(知らなかった)。CarebonLib 1.4 SDKで利用されているUnversal Interfacesは3.4.1b1なのですが、ADC メンバーサイトには既に3.4.1b2が登録されていますので、差し替えた方が良いかもしれません。

Quartz 2Dでは、描画対象となる環境を「Graphics Context」(CGContextRef)と言います。QuickDarawで言えば、CGrafPortに相当するデータタイプだと考えてください。Quartz 2DがContextとして認めている環境、つまりQuartz 2Dの描画対象となりうる環境には、Window(もしくはスクリーン)、プリンター、PDFファイル、Bitmap(QuickDrawのBitMapとは異なる)などがあります。こちらも、QuickDrawの、Window、プリンター、PICTファイル、PixMap(GWorldオフスクリーン)に相当すると考えれば理解しやすいはずです。ただし、Quartz 2Dには、QuickDrawのカレントポートに相当する考え方は無く、SetPort()のようなAPIもありません。Quartz 2DのほとんどのAPIには、引数に「CGContextRef」を渡すことができ、それにより対象となるContextを判断するようになっています。これにより、QuickDrawでよく遭遇する、「今、描画しているウィンドウはどれだっけ?」と言った混乱(バグ)を回避できます。

PDFファイルの場合、解像度に依存しない状態で複数ページに出力可能な点が、他のContextとは異なるようです。また、Quartz 2Dが書き出すPDFファイルは、「PDF Reference Version 1.3」に準拠していると記載されています。興味ある方は、以下のAdobeサイトからドキュメントをダウンロードし、仕様の詳細を確認してみると良いでしょう。(696ページもありますが...)

◇Adobe Solutions Network: Developer Program
 http://partners.adobe.com/asn/developer/technotes/acrobatpdf.html

ただし、WWDC 2001のセッションでは、「現状では、Version 1.3の仕様をすべて満たしていない」というAppleの技術者の話しもありましたので、用途によっては注意が必要かもしれません。その他にも、プリントアウト時のTacnsparency(透過処理)ができないといった制限もあるようです。

各Contextには、それぞれ描画時のラインの太さや表示カラーなどの状態が保存されています。Quartz 2Dではこれを「Graphics State」と呼んでいます。QuickDrawでも、ライン幅やカラー、文字サイズやフォントなどの値が各CGrafPortごとに保存されていましたが、それとまったく同じです。ただし、QuickDrawと違うのは、こうしたGraphics Stateを、CGContextSaveGState()でスタックに待避させたり、CGContextRestoreGState()で復帰させたりすることができる点です。これは、OpenGL(Macintoshに搭載されている3Dライブラリー)を使用したことがある人には、お馴染みの手法でしょう。また、Graphics StateにはCurrent Transformation Matrizx(CTM)と呼ばれる座標変換用マトリックスが用意されており、これを色々と変更することで、QuickDrawでは不可能だった文字の回転などの処理も簡単に行うことができます。こうした座標変換マトリックスについても、QuickTimeのGraphic ImporterやMovie Toolboxを利用したことがある人なら、お馴染みかもしれません。例えば、具体的に文字を回転描画するには...

1.CGContextSaveGState() で現在のGraphics Stateを待避させる。
2.CGContextRotateCTM() でCTMに回転マトリックスをセットする。
3.文字を普通に描画すれば座標中心に回転されて描画される。
4.CGContextRestoreGState()で待避させていたGraphics Stateを戻す。

といった手法を取ります。

では、さっそくQuartz 2Dの描画環境(Context)を作成してみることにします。この時デベロッパーとして考慮しておきたい点は、Quartz 2DとQuickDrawの両描画環境を同じ場所にセットするということです。でないと、どちらか片方のグラフィックシステムの描画機能しか利用できないことになり(それでも良ければ問題ないですが)色々とできる事に制限が付いてしまいます。

WindowからContextを作るのには、CreateCGContextForPort()というAPIを利用すれば簡単です。引数にWindowのCGrafPtrを渡せば、作成されたContextがCGContextRefとして返されます。

 

こルーチンの後半は、Quartz 2Dの描画座標系をQuickDrawに一致させる処理を行っています。QuickDrawでは、描画矩形領域(CGrafPort)の左上が原点でY軸は下向きがプラスです。それに対してQuartz 2Dでは、矩形領域の左下が原点でY軸は上向きがプラスです。もし、両システムによる描画を単一座標系で実行したいのなら両座標系を合わせておきます。SyncCGContextOriginWithPort()実行すると、SetOrigin()で移動された原点がContextへて反映されます。次のCGContextTranslateCTM()で原点を左上に移動し、CGContextScaleCTM()でY軸のプラス方向をひっくり返します。この処理により、マウスクリックした座標なども、変換せずQuartz 2D APIに渡せるようになります。ただし、CTMを利用した座標系の変換を必ず行う必要は無く、状況により臨機応変に最善な方法を選べば良いと思います。ちなみに、CreateCGContextForPort()は、Core Graphicsに関係したヘッダーファイルに定義されておらず、QuickDraw.hに定義されていますので注意してください。

続いて、NewGWorld()で作成したオフスクリーン用CGrafPortを、Contextとして採用してみます。

 

すなわち、オフスクリーンとして用意したPixMap(Quartz 2Dで言うところのBitmap)をContextにすることに相当します。こうすれば、Quartz 2DとQuickDraw両方で、オフスクリーンへの描画が可能になるはずです。ところが何故だか、CreateCGContextForPort()にNewGWrold()で得たGWorldPtrを渡すと、「パラメータが異常」というエラーが返ってきてContextが作成できません。Appleに確認してみたところ、これはMac OS X 10.0.4に実装されているQuartz 2Dの制限だそうで、将来的には大丈夫だそうです。

ならば、NewGWorld()で確保したPixMapから、CGBitmapContextCreate()を使い直接Contextを作成すれば大丈夫だろうと考えたのですが、APIを呼び出すと同じようなエラーが返ってきます。

 

どうも、このAPIの5番目の引数「bytesPerRow」の値は、きっちりwidth(矩形の幅)*4 Byteでないと受け付けないのが原因のようです。ところが、NewGWorld()が作成するグラフポートのPixMapのrowBytesは、(width*4)+16バイトとなっています。そのためエラーが返ってくるわけです。こちらも、Appleに確認したところ、あっさり「バグです」と言われてしまいました(涙)。

仕方ないので、NewGWorld()で確保したPixMapのRowByteを強制的にwidth*4 Byteに変更し、再度CGBitmapContextCreate()に渡したところ、問題なくCGContextRefを得ることが出来ました。

 

試しに、QuickDrawとQuartz 2Dの両方を使い同じ図形を描画してみましたが問題ありませんでした。ただし、この方法はあくまでも暫定的なものであり、下手をするとQuickDrawの描画パフォーマンスを落としてしまう可能性がありますので注意してください。(Appleさん、早くバグをとってください)

次回は、自作CarbonアプリケーションでQuartz 2Dを利用し、実際に何か図形を描画してみます。できたら、QuickDrawのPixMapイメージをQuartz 2Dで描画するとにもチャレンジしてみます。
[小池邦人/オッティモ]
関連リンクオッティモ