このページは 書籍 Delphi Graphic Secrets
に書ききれなかった、Delphi
のグラフィック・プログラミングのノウハウをアットランダムに紹介しています。
最終更新日: 2002/7/17
|
2. TBitmap.Scanline を使う際の注意書籍では TBitmap の Scanline プロパティは DIB Section のスキャンラインの先頭を指すポインタを返すと書きました。これは事実ですが若干の注意事項があります。 TBitmap が DDB を保持している場合、ScanLineプロパティが参照されると TBitmap は 自動的にビットマップを DDB から DIB Section に変換し、その ScanLine のポインタを返します。ところが、TBitmap は DDB も保持しつづけます。ここでさらに ScanLine プロパティを参照すると、なんと TBitmap はまた最初から DDB→DIB Section の変換をやり直すのです! このため、TBitmap が DDB を保持しているときは ScanLine プロパティは結局正常には動きません。 Scanline プロパティを使うときは PixelFormat プロパティか HandleType プロパティで TBitmap が DIB Section を持つように設定してから Scanline プロパティを使ってください。 |
3. Canvasが消える?例えばPaintBoxの内容をフォームにコピーしたい時
と書くことがあるでしょう。実はこの処理は動かないことがあります。 VCL ではコントロールのクライアント領域用のデバイスコンテキストは最大4個に制限されています。これは Windows 95 系列の Windows ではコントロール用の GetDCで取得したデバイスコンテキスト数が5個を越えると不正な動作をするからです。VCL は Controls ユニットの CanvasList変数でこれを管理していて、コントロールの デバイスコンテキスト数が4を越えようとすると、最も古く CanvasList に登録されたコントロールのデバイスコンテキストを 破棄します。 上のコードでは、CopyRect メソッドの内部動作において、フォームのデバイスコンテキストハンドルを取得後、PaintBox1 の Canvas のデバイスコンテキストが作成される可能性があり、従ってフォームのデバイスコンテキストが描画時に破棄されている可能性があります。 デバイスコンテキストの自動破棄はロックされている Canvas には適用されません。VCLはロックされていない別のデバイスコンテキストを削除しようと試みます。従って、
とすれば常に正常に動作します。 |
4. パターンマネージャGraphics ユニットには、フォントマネージャ、ペンマネージャ、ブラシマネージャがありますが、これに加えてパターンマネージャというものが有ります。これはブラシパターンの作ってくれるサービスです。 Graphics ユニットの function AllocPatternBitmap(BkColor, FgColor: TColor): TBitmap; を呼び出すと、BkColor, FgColor で市松模様になった 8x8 のビットマップが戻ります。VCLはこのブラシパターンをメニューや Drag&Dock の描画で使用しています。これはパレットをもたないビットマップですので、予約色以外の色は指定しないようにしてください。 AllocPatternBitmap には若干注意点があります。AllocPatternBitmapは作成したビットマップをキャッシュし、同じパターンをひとつしか作りません。AllocPatternBitmapが返すパターンビットマップはパターンマネージャが管理しています。ですからAllocPatternBitmapが返すビットマップをフリーしてはいけません。注意してください。 |
5. Canvasの寿命TCanvas の Handle プロパティは デバイスコンテキストのハンドルです。書籍 Delphi Graphic Secrets では パレットの処理などでデバイスコンテキストのハンドルを使う処理を数多く紹介していますが、デバイスコンテキストハンドルの寿命について書き忘れていました。ここでデバイスコンテキストハンドルの寿命についてお話します。 VCL にはデバイスコンテキストにはいくつかの種類があります。
このなかで問題となるのは、2と3です。これらをここでは「寿命の短い Canvas」 と呼ぶことにしましょう。 Windows 95系列の Windows ではご存知のように 64KB しかない小さな GDI ヒープの中に 各種の GDI オブジェクト(ペン、ブラシ、..etc))を作ります。全てのアプリケーションは GDIヒープを共用します。デバイスコンテキストもここに作られます。 デバイスコンテキストは GDI オブジェクトの中では比較的大きなものなので(数百バイト)、あまり多く作ると OS がクラッシュしてしまいます。 この問題に対処するため、VCL はデバイスコンテキストを減らす仕組みを持っています。 WIndows のアプリケーションはメッセージを受信して処理を行うわけですが、VCL ではウィンドウを持つコントロールは、メッセージを MainWndProc というメソッドで受け取って、そこから WndProcメソッドを呼び出してメッセージ処理を行います。メッセージ処理が終わると、MainWndProc メソッドは最後に
こうすることで、アプリケーションが保持しているデバイスコンテキストを必要最小限に保つわけです。 このデバイスコンテキストの自動破棄は、デバイスコンテキストを Canvas のメソッドやプロパティを介して使っている場合は問題にはなりません。Canvas はデバイスコンテキストが必要になると作り直し、適切に設定しなおすので、処理の途中でデバイスコンテキストが破棄されても、そのことを Canvas の利用者は感知しなくてよいからです。 しかし、Canvas のデバイスコンテキストハンドル(Handleプロパティ)を直接使う場合は話が違ってきます。 例えば、デバイスコンテキストにパレットやリージョンを選択して描画処理を行っている時、その処理の中でメモコントロールのプロパティなどを弄るとメッセージが発生することがあります。するとその瞬間にデバイスコンテキストは破棄されてしまいます。もちろんコントロールにメッセージを SendMessage API で送ったりしてもいけません(PerFormメソッドは一応大丈夫ですが、他のメッセージを生み出す危険性があります)。 書籍の中で説明したパレットの実体化処理もやり方によっては問題を起こします。 SelectPalette の第2パラメータを False にしてパレットを選択し実体化すると、それがフォアグラウンド実体化になった場合、WM_PALETTECHANGED メッセージが発生することがあります。ですから、パレットがデバイスコンテキストに選択され実体化した瞬間、デバイスコンテキストが消滅してしまうという事態になります。書籍の中でパレットを使って描画する際、 SelectPalette の第2パラメータに True を指定するように書いたのはこういう理由もあるからなのです。 以上のように、寿命の短い Canvasのデバイスコンテキストハンドルを直接使うには細心の注意が必要です。その必要が無いなら避けてください。必要なら、最小限にとどめるようにしましょう。 |
6. RGB-HLS 変換Delphi 6 には密かにグラフィック用として GraphUtil.pas というユニットが追加されています。基本的には ActionToolBar 用なのですが、この中に RGB-HLS変換のルーチンが入っています。このユニットはヘルプに載っていません。 色を HLS で表すと、もう少し濃い目にとか、もう少し明るめにとかいった色の調整が楽にできます。ActionToolBar はこれを利用して、ハイライトカラーや影用の色を基準となる色から作っています。これらの関数、手続きはアプリケーションからでも利用できます。
尚、H, L, S は 0〜240の値をとるように設計されています。 |
7. TGraphic のコンストラクタDelphi Graphic Secrets の「 3-6 カスタムグラフィックスクラスの作成」では取り上げませんでしたが、Graphics ユニットの TGraphic のコンストラクタが protected になっているという問題は、いろいろな場で取り上げられ批判されてきました。 なぜこれが問題なのかというと、TGraphic はグラフィッククラスの抽象クラスで、グラフィッククラスは仮想コンストラクタを使って多態的にインスタンスを作れるように設計されているからです。 例えば、TPicture の Graphic プロパティにグラフィックを代入すると、グラフィックの「コピー」が Graphic プロパティに代入されます。これが可能なのは、代入するグラフィックと同じ型のグラフィックを TPicture 内に生成することが可能なためで、これには仮想コンストラクタの利用が必要不可欠なのです。 仮想コンストラクタを使うにはその元締めとなる抽象クラスに public な仮想コンストラクタが必要です。Graphicsユニット内ではコンストラクタの可視性が無視されるので問題は起きませんが、Graohicsユニットの外では public になっていないと仮想コンストラクタは使えず、多態的にグラフィックを生成することができません。つまり、TPicture の Graphic プロパティと同等の使い勝手を実現することが Graphic ユニットの外ではできないのです。これは困った制約で、プログラマを長年困らせてきました。 幸いなことに Delphi 6 では TGraphic のコンストラクタが public になり、この Delphi 発売以来の不具合が終結しました。多くのプログラマがほっとしたのではないかと思います。 Delphi 6 で TGraphic のコンストラクタが public になっていることを前提にしたプログラムは Delphi 5 ではコンパイルエラーにならない上、奇妙な振る舞いをします。Delphi 5 以前の版をお使いの方は今後ご注意ください。DHGL は今のところこの点に依存したコードはありません。 |
8. パスはリージョンに変換できるDelphi Graphic Secrets では取り上げませんでしたが、パスはリージョンに変換できます。これには様々な応用がありますが、グラデーション付きの文字列を描く例を紹介しましょう。
|
これを実行すると下の図のようになります。
コードを若干説明しておきましょう。コードでは、まず、パスに'Delphi'
という文字列を描き、PathToRegion API でリージョンに変換します。PathToRegion
API はパスの閉じた図形の中身をリージョンにします。
こうして作成されたリージョンをアプリケーション定義クリッピングリージョンにしてその上に矩形のグラデーションを描けば上の図のようにグラデーションが付いた文字が描けるわけです。 但し、Delphi Graphic Secrets で説明したように、文字を囲む矩形領域を求めるのは厄介な仕事です。イタリック体の文字は、Canvas.TextWidth が示す幅より文字幅が大きくなることがあるからです。しかし、これは DHGL の TextUtils ユニットにある GetTextAAdjust, GetTextCAdjust を使えば簡単に補正量を求めることができます。これを使って、上のコードでは、描く矩形の位置の計算を行っています。これは DHGL が無いとやっかいな作業です(^^ |
9. ビットマップのグレースケール化ビットマップを256階調のグレースケールに変換するにはいくつかの方式が有ります。
この3つの方式と、ビデオドライバにグレースケール化を任す方式の計4種類の方式でグレースケール化を行った例を以下に示します。 左上が原画像。その右隣が、ビデオドライバがグレースケール化を行った結果で、その右隣が NTSC加重平均法の結果です。 2段目は、それぞれ中間値法、単純平均法の結果です。 NTSC方式以外では、青が明るくなりすぎる傾向があり、また中間値法と単純平均法はコントラストが弱くなる傾向があるようです。NTSC方式が最も優れているようです。 上記4方式を実現する関数 ColorToGraySimple(ドライバに任す), ColorToGrayNTSC(NTSC加重平均法), ColorToGrayMed(中間値法), ColorToGrayAve(単純平均法)を含むユニット ColorToGray.pas を以下に示します。 これらの関数は任意の形式のビットマップをグレースケール化し、256階調グレースケールパレットを持つ 8bpp のビットマップを返します。 TBitmap用とTBigBitmap 用の両方の関数が含まれていますのでご活用ください。
|