タイトル小池邦人のプログラミング日記》2001/9/1<Quartz 2D(Core Graphics)を使う その4>カテゴリーグラフィックス, 小池邦人のプログラミング日記
作成日2001/9/3 14:26:20作成者新居雅行
今回は、QuickDrawのイメージ(PixMapイメージ)をQuartz 2Dで描画することにチャレンジしてみます。Quartz 2Dでは、描画するイメージをBitmapと呼ぶのですが、これはQuickDrawのBitMap(白黒2値イメージ)とは異なる物なので注意してください。

最近、AppleのADCメンバーサイトに「CarbonLib 1.4.5d3 SDK」が登録されました。まだまだCarbonLibの開発は続くようです(笑)。逆に言えば、未だに実現していない機能とか、取れていないバグが存在していると言うことですから、利用する側としては、必ずSDKに添付されているRelease Notesには目を通すべきでしょう。ただし、今回追加されたAPIやバグフィックスの箇所を見てみると、ほとんどシステムの奥底であり、一般的なデベロッパーが活用する箇所については、かなり「こなれて」来たのかもしれません。ちなみに、今回のSDKに付属しているUniversal Interfacesのバージョンは3.4.1b2で、こちらに関しては、ADCメンバーサイトに登録されている最新バージョンからの変更点は無いようです。

さて、Quartz 2Dを使い、特定のContex(描画環境)へイメージを描画する場合には、CGContextDrawImage()というAPIを利用します。このAPI、QuickDrawでイメージを描画する時によく使うCopyBits()とは少し性質が異なります。どちらかと言うと、QuickDrawのDrawPicture()に似ているかもしれません。CopyBits()の方は、汎用のイメージ転送ルーチンであり、ダイレクトに転送元と転送先を選択でき、描画モードについても(Copy、OR、XORなど)色々と変更することができました。それと比較して、CGContextDrawImage()は、指定されたイメージデータを、単純にContextの特定矩形領域に描画するだけです。ただし、オリジナルからの拡大、回転、変形といった処理は、以前に説明したCurrent Transformation Matrizx(CTM)を操作することで簡単に実現することができます。描画結果の品質は、アンチエイリアスもしっかり処理されており、QuickDrawのCopyBits()とは比較にならないほど美しいものです。Mac OS Xのスクリーンセーバーに、フォルダに保存されているイメージをスライドショー的に順次表示していくモジュールがありますが、あの時の美しい拡大表示は、この能力を活用していると思われます。

まず最初に、Windowへ描画したいイメージをファイルからロードし、QuickDrawのPixMapイメージとしてGWorldオフスクリーンバッファに保存しておきます。ここまでの作業は、ToolBoxやQuickDraw APIを活用して処理する一般的な作業ですので、説明は省略します。そうして確保したオフスクリーンバッファを参照するためのWindowは、先んじて外部変数(WindowRef)のq_wptrに保存しておきます。以下が、GWorldに確保したPixMapをQuartz 2DのBitmapイメージに変換し、それをWindow(Context)に描画するためのdrawPixMapImage()ルーチンです。

 

このルーチンの引数は、保存されているPixMapを参照するためのWindowRefと、描画先を決定するQuartz 2D Context、イメージの描画位置を示すX、Y座標(Point)です。

最初のlockOffScreen()は、GWorldが内部に確保しているPixMapのメモリー領域(Handle)をロックするための自作ルーチンです。続いて、GetPixBaseAddr()とGetPixRowBytes()を使い、PixMapイメージの先頭アドレスとRowバイト(画像の横幅のバイト数)を得ます。それと、GetPortBounds()で得たイメージの矩形枠(prt)から、それの横幅ピクセル数(ww)、高さピクセル数(hh)、そして全データのバイト数(size)を計算しておきます。まずは、イメージの先頭アドレスとデータバイト数をCGDataProviderCreateWithData()に渡し、CGDataProviderRefを得ます。この処理により、描画したいイメージのデータ供給元を特定したことになります。

CGColorSpaceCreateDeviceRGB()で採用するカラースペック(CGColorSpaceRef)を決定し、他の引数と伴にCGImageCreate()に渡し、Quartz 2D BitmapイメージのリファレンスであるCGImageRefを得ます。引数のkCGImageAlphaPremultipliedFirst定数は、このBitmapイメージがどのようなタイプなのかを指示しています。Quartz 2Dで描画できるイメージには、アルファーチャンネルやRGBプレーンの並び順の違いで、色々なタイプが存在しています。そこうしたタイプについては、Universal InterfacesのCGImage.hに定義されていますので、一度参照してみてください。CGImageRefを、CGRectMake()で作成した矩形情報と一緒にCGContextDrawImage()に渡せば、同じく引数として渡したContexの指定矩形領域に、イメージが描画されることになります。

この時CGRectMake()に渡す引数は、SetRect()とは異なり4つとも浮動小数点で、渡する順序も矩形の左、下、幅、高さとなっていますので注意してください。確保したCGColorSpaceRefやCGImageRefは、作業終了後に、CGColorSpaceRelease()やCGImageRelease()で解放しておきます。ルーチンの最後に実行しているunlockOffScreen()は、GWorldのPixMapイメージのメモリー領域のロックを解除する自作ルーチンです。

drawPixMapImage()を用い、Window上のマウスクリック位置にイメージを描画するpaintImageWindowX()ルーチンは、以下のようになります。

 

確保したイメージは、マウスドラッグをしている間(マウスを離すまで)連続して描画されます。対象イメージは、drawPixMapImage()に外部変数に確保したq_wptrを渡すことで指示しています。ただし、この処理だと、描画する度にCGImageCreat()でイメージを作成することになり、描画効率が良くありません。本当ならば、paintImageWindowX()で実行されるマウスクリックループの直前に、CGImageCreate()でCGImageRefを得て、マウスが離されたらCGImageRelease()で解放するように処理に書き直すべきでしょう。そうすれば、より高速なイメージ描画が可能となります。

さて、こうしたルーチンを使い、実際にGWorldに確保してあるイメージをWindowに描画してみると、困ったことが起こります。

 

図の左側にあるのが、GWorldに確保されているオリジナルイメージです。その右側のWindow上が、実際の描画中を示しているのですが、描画イメージがひっくり返っていることが分かります。これは、以前に解説したcreateWindowContxt()が、Quartz 2Dの描画座標系をQuickDrawやマウスカーソルの座標系に合わせるために、CTMを調整しているのが原因です。これにより、描画位置はマウスカーソルと一致したのですが、イメージの描画方向まで座標変換されてしまったわけです。

これを防ぐためには、イメージのCGImageRefを得る時に、メモリ上のイメージの方向を再調整する必要があります。しかし、それは非常に面倒な作業となりますので、座標変換をしているCTMの調整を止め、独自に座標変換をするように改良した方が得策です。以下が、CTMによる調整を止めたcreateWindowContxt()ルーチンと、独自の座標変換を追加したpaintImageWindowX()ルーチンです。

 

座標変換を行うのはマウスカーソルのY座標だけであり、その処理も...

pt1.v=(drt.bottom-drt.top)-pt1.v; /* pt1.vはマウスのローカルY座標 */

といった簡単なものです。よって、イメージだけでなく図形を描画する場合にも、こちらを用いた方が良いかもしれません。この新しいcreateWindowContxt()とpaintImageWindowX()を使えば、Window上へのイメージの描画結果は以下のように正常に行えます。

 

次回は、Quartz 2Dを利用したPDF(Portable Document Format)ファイルの描画と、描画図形のPDFファイルへの書き出し(保存)を解説してみたいと思います。
[小池邦人/オッティモ]
関連リンクオッティモ