今回,あるところから依頼されたアプリケーションでたくさんの星を描く必要性に迫られたため, 元絵を使わずにコードで星を描く方法を検討した. 成果をまとめておく. 下図は,以下で示すDrawStar関数を使ってフォームの任意位置に星を100個描いたところである.
図は円に内接する星型を示している.
CanvasのPolygonメソッドに,頂点AからJまでの点の座標を与えれば
星型を描くのは簡単である.
図中のA,B,C,DおよびEの座標は円に内接する正五角形の頂点であるから,
中心Oの座標と円の半径が与えられれば決定するが,
F,G,H,IおよびJの座標の決定はかなり面倒である.
ちなみに,Oの座標を(x0,y0)とし円の半径をRとすれば,A,B,C,DおよびEの座標は以下のようになる(yは下向きが正).
A: x = x0 , y = y0 - R
B: x = x0 + R sin θ, y = y0 - R cos θ
C: x = x0 + R sin( 2θ ), y = y0 - R cos( 2θ )
D: x = x0 - R sin( 2θ ), y = y0 - R cos( 2θ )
E: x = x0 - R sin θ, y = y0 - R cos θ
ここでθ=72*π/180である.
実は,PolygonメソッドにA,B,C,DおよびEの各座標をA,C,E,B,Dの順で与えるだけで
一筆描きの要領で星型を描いてくれる.
また,そのときのBrushの色で内部を塗りつぶしてくれる.
例えば,星を描きたい領域がARectならば以下のようにすればよい.
var
Theta: Double;
Org: TPoint;
Points: array[ 0..4 ] of TPoint;
(中略)
Theta := Pi * 72 / 180;
R := Round( ( ARect.Right - ARect.Left ) / 2 );
Org.x := R;
Org.y := R;
Points[ 0 ] := Point( Org.X, Org.Y - R );
Points[ 1 ] := Point( Org.X + Round( R * Sin( 2 * Theta ) ),
Org.Y - Round( R * Cos( 2 * Theta ) ) );
Points[ 2 ] := Point( Org.X - Round( R * Sin( Theta ) ),
Org.Y - Round( R * Cos( Theta ) ) );
Points[ 3 ] := Point( Org.X + Round( R * Sin( Theta ) ),
Org.Y - Round( R * Cos( Theta ) ) );
Points[ 4 ] := Point( Org.X - Round( R * Sin( 2 * Theta ) ),
Org.Y - Round( R * Cos( 2 * Theta ) ) );
Canvas.Brush.Color := clRed;
Canvas.Pen.Color := clRed;
Canvas.Polygon( Points );
星の内部が塗りつぶされない. APIのドキュメント等を調べてみると, 内部が塗りつぶされないのは,Canvasが対象としている デバイスコンテキスト(DC)の塗りつぶしモードがALTERNATEになっているためということがわかった. 内部が全部塗りつぶされるようにするには,塗りつぶしモードをWINDINGに変更すればよい. 塗りつぶしモードの変更にはSetPolyFillModeを用いるが, このAPIのヘルプには,
In general, the modes differ only in cases where a complex, overlapping polygon must be filled (for example, a five-sided polygon that forms a five-pointed star with a pentagon in the center). In such cases, ALTERNATE mode fills every other enclosed region within the polygon (that is, the points of the star), but WINDING mode fills all regions (that is, the points and the pentagon).
と書かれている.
まさに今問題としていることである.
上のコードでPolygonメソッドを呼び出す前に,
SetPolyFillMode( Canvas.Handle, WINDING );
余談になるが,VCLのソースを見ると,CanvasのPolygonメソッドやPolylineメソッドは受け取ったTPoint型の配列を
APIのPolygonやPolylineに渡しているだけである.
例えば,Polygonのプロトタイプは
function Polygon( DC: HDC; var Points; Count: Integer ): BOOL; stdcall;
Polygon( Canvas.Handle, Points, 5 ):
type
PPoints = ^TPoints;
TPoints = array[0..0] of TPoint;
Polygon( Canvas.Handle, PPoints( @Points )^, 5 ):
さて,以上で一応の目的は達せられたわけであるが,今度は星の輪郭を描くことを考えてみる.
単純に,上のコードで
Canvas.Brush.Color := clRed;
Canvas.Pen.Color := clBlack;
のように内部にまで線が引かれてしまうのである.
いろいろと試行錯誤してみたが,輪郭を描くにはリージョンを使うしかなさそうだ. リージョンとはDCの描画領域を制限する(クリッピングする)形状のことである. 例えば,円形のリージョンを作成しDCに選択すると,FillRectなどでDC全体を 塗りつぶしても円形リージョンの中だけが塗りつぶされる. ここに置いてある「アイ」もリージョンを使っている.
Delphiではリージョンがサポートされていないので,
リージョンの作成やDCへの選択はAPIを直接使う必要がある.
以下の例はフォームのOnPaintイベントの中で円形のリージョンを作成してフォームのDCに選択し,
ビットマップを描画するものである.
procedure TForm1.FormPaint(Sender: TObject);
var
RGN: HRGN;
Bmp: TBitmap;
begin
Bmp := TBitmap.Create;
Bmp.LoadFromFile( 'otoha3.bmp' );
RGN := CreateEllipticRgnIndirect( Rect( 30, 20, 150, 140 ) );
SelectClipRgn( Canvas.Handle, RGN );
Canvas.Draw( 0, 0, Bmp );
SelectClipRgn( Canvas.Handle, HRGN( nil ) );
DeleteObject( RGN );
Bmp.Free;
end;
星の輪郭を描く具体的な手順は以下のようになる.
1. CreatePolygonRgnで星型のリージョンを作成する
2. PaintRgnでリージョン内部を塗りつぶす
3. FrameRgnでリージョンの輪郭を描く
4. リージョンを解放する.
CreatePolygonRgnのプロトタイプは以下のようになっている.
function CreatePolygonRgn( const Points; Count, FillMode: Integer ): HRGN; stdcall;
リージョンの内部を塗りつぶしたり輪郭を描く場合は,いちいちSelectClipRgnでDCに選択/解除しなくても, PaintRgnやFrameRgnにDCのハンドルとリージョンのハンドルを両方渡せばよいようだ. おそらくこれらのAPIの中で選択/解除を行っているのだろう.
星型を描画する汎用的な手続きDrawStarを作ってみた.
引数として,描画対象となるCanvas,描画領域のRect,星の内部色,背景色,輪郭色
および背景を透明にするか否かのフラグを受け取る.
背景を透明にする場合は,背景色として指定された色が透過色となる.
Canvasに直接描画するのではなく,
テンポラリなメモリビットマップを作成し,これに描画したのち対象となるCanvasに転送する,
いわゆるダブルバッファリングを使っている.
procedure DrawStar( Canvas: TCanvas; ARect: TRect;
ForeColor, BackColor, FrameColor: TColor;
Transparent: Boolean );
var
Theta: Double;
Org: TPoint;
R, W, H: Integer;
RGN: HRGN;
Bmp: TBitmap;
Points: array[ 0..4 ] of TPoint;
begin
Theta := Pi * 72 / 180;
R := Round( ( ARect.Right - ARect.Left ) / 2 );
Org.x := R;
Org.y := R;
Points[ 0 ] := Point( Org.X, Org.Y - R );
Points[ 1 ] := Point( Org.X + Round( R * Sin( 2 * Theta ) ),
Org.Y - Round( R * Cos( 2 * Theta ) ) );
Points[ 2 ] := Point( Org.X - Round( R * Sin( Theta ) ),
Org.Y - Round( R * Cos( Theta ) ) );
Points[ 3 ] := Point( Org.X + Round( R * Sin( Theta ) ),
Org.Y - Round( R * Cos( Theta ) ) );
Points[ 4 ] := Point( Org.X - Round( R * Sin( 2 * Theta ) ),
Org.Y - Round( R * Cos( 2 * Theta ) ) );
W := ARect.Right - ARect.Left;
H := ARect.Bottom - ARect.Top;
Bmp := TBitmap.Create;
try
// Canvasと互換性のあるビットマップを作成する
Bmp.Handle := CreateCompatibleBitmap( Canvas.Handle, W, H );
Bmp.Transparent := Transparent;
Bmp.TransparentColor := BackColor;
Bmp.Canvas.Brush.Color := BackColor;
Bmp.Canvas.FillRect( Rect( 0, 0, W, H ) );
// 星型のリージョンを作成し,塗りつぶす
RGN := CreatePolygonRgn( Points, 5, WINDING );
Bmp.Canvas.Brush.Color := ForeColor;
PaintRgn( Bmp.Canvas.Handle, RGN );
// リージョンの輪郭を描く
Bmp.Canvas.Brush.Color := FrameColor;
FrameRgn( Bmp.Canvas.Handle, RGN, Bmp.Canvas.Brush.Handle, 2, 2 );
// リージョンを破棄し,ビットマップをCanvasに描画する
DeleteObject( RGN );
Canvas.Draw( ARect.Left, ARect.Top, Bmp );
finally
Bmp.Free;
end;
end;
DrawStar( Canvas, Rect( 0, 0, 200, 200 ), clRed, clOlive, clBlack, True );
プログラミングをよく知らない人からみれば,星を描くなんて簡単なことのように思われるかもしれないが, コードで書くのは案外難しいのである.
| SEO | [PR] 花 おまとめローン Windows7 冷え性対策 | 動画 掲示板 レンタルサーバー ライブチャット SEO | |