Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2017年05月28日] [更新 2017年05月28日] GDI+ 関係サンプル
GDI+ 関係サンプル 
G178_パスをリージョンにして三日月のような図形を作成
動作確認等 Windows 7 U64(SP1) + Delphi XE(UP1) Pro
G178_GDIPlus_MoonShape.zip [2,879 KB] 2017年05月28日版 (EXE 同梱)



  • 2013年07月22日
  • 画像のプロパティタグの分数形式プロパティの配列を,動的配列を使用する方式に変更




パスをリージョンにして領域を設定


Resion (リージョン) を直訳すると「領域」です.したがって,本項のタイトルは違和感であると思いますが,ここでいうリージョンは,画像を扱うプログラムにおける用語と思ってください.

図形を作成する際にハスを生成して作成すると,各々の図形,あるいは複数の図形をリージョンに設定できます.パスは複数の図形を結合しますが,その中の特定の「領域」だけを操作の対象にしたい場合,TGPResion の機能を使用して GDI+ でいうところの領域として設定できます.

ここでは,Halbow 資料館に掲載されているような三日月形の図形を作成します.






01_三日月のような図形の作成


下図は下に示すコードを実行した結果です.パスとしての円を 2 つ描画しています.この結果を見ると,最初に描画した左川の円の領域から次に描画している右側の円の領域を除けば三日月のような形にできると予測できます.


図1
2 つの円をパスで作成して描画
図2
左側の図形の領域
  • 右側の図形の領域が重なっている
  • この重なっている部分を取り除けば三日月形の図形になる
  • つまり,左側の図形だけの部分を「領域」にすればよい

リスト1
2 つの円をパスで別々に作成して描画
implementation

uses
  GDIPAPI,
  GDIPOBJ;

{$R *.DFM}

//=============================================================================
//  フォームのOnPaintイベント処理
//=============================================================================
procedure TForm1.FormPaint(Sender: TObject);
var
  LLeft      : Single;
  LTop       : Single;
  LWidth     : Single;
  LHeight    : Single;
  LGPGraphic : TGPGraphics;
  LGPPath1   : TGPGraphicsPath;
  LGPPath2   : TGPGraphicsPath;
 LGPPen     : TGPPen;
begin
  LGPGraphic := TGPGraphics.Create(Canvas.Handle);
  //背景を白で塗り潰す
  LGPGraphic.Clear(aclWhite);


  LLeft   := 40;
  LTop    := 30;
  LWidth  := 150;
  LHeight := 150;

  //パスのオブジェクトを生成
  LGPPath1 := TGPGraphicsPath.Create;
  LGPPath2 := TGPGraphicsPath.Create;

  //色と線の太さを指定してペンを生成
  LGPPen := TGPPen.Create(aclBlack, 5.0);

  //円のパスを2つ生成
  LGPPath1.AddEllipse(LLeft,      LTop, LWidth, LHeight);
  LGPPath2.AddEllipse(LLeft + 50, LTop, LWidth, LHeight);

  //パスを描画
  LGPGraphic.DrawPath(LGPPen, LGPPath1);
  LGPGraphic.DrawPath(LGPPen, LGPPath2);

  LGPGraphic.Free;
  LGPPen.Free;
  LGPPath1.Free;
  LGPPath2.Free;
end;


実際の実行結果です.左側の円をリージョンにして,右側の円の領域を TGPRegion.Exclude で除外しました.リージョンにした左側の円の領域に含まれる右の円の部分を除外したことになります.
リージョンそのものは「領域」ですから,領域を設定してもどの部分かは目では見えません.そこで,設定した領域,つまりリージョンを塗り潰しています.塗り潰しにはブラシを使います.


図3
実行結果

リスト2
三日月のような図形を作成するコードの例
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  ExtCtrls, StdCtrls;

type
  TForm1 = class(TForm)
    procedure FormPaint(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses
  GDIPAPI,
  GDIPOBJ;

{$R *.DFM}

//=============================================================================
//  フォームのOnPaintイベント処理
//=============================================================================
procedure TForm1.FormPaint(Sender: TObject);
var
  LLeft      : Single;
  LTop       : Single;
  LWidth     : Single;
  LHeight    : Single;
  LGPGraphic : TGPGraphics;
  LGPPath1   : TGPGraphicsPath;
  LGPPath2   : TGPGraphicsPath;
 LGPPen     : TGPPen;
  LGPBrush   : TGPSolidBrush;
  LGPRegion  : TGPRegion;
begin
  LGPGraphic := TGPGraphics.Create(Canvas.Handle);
  //背景を白で塗り潰す
  LGPGraphic.Clear(aclWhite);


  LLeft   := 40;
  LTop    := 30;
  LWidth  := 150;
  LHeight := 150;

  //パスのオブジェクトを生成
  LGPPath1 := TGPGraphicsPath.Create;
  LGPPath2 := TGPGraphicsPath.Create;

  //色と線の太さを指定してペンを生成
  LGPPen := TGPPen.Create(aclBlack, 5.0);
  //
  LGPBrush := TGPSolidBrush.Create(aclLightGreen);

  //円のパスを2つ生成
  LGPPath1.AddEllipse(LLeft,      LTop, LWidth, LHeight);
  LGPPath2.AddEllipse(LLeft + 50, LTop, LWidth, LHeight);

  //パス1の領域をリージョンとしてパス2の部分を除外する
  LGPRegion := TGPRegion.Create(LGPPath1);
  LGPRegion.Exclude(LGPPath2);

  //リージョンを塗り潰す
  LGPGraphic.FillRegion(LGPBrush, LGPRegion);

  LGPGraphic.Free;
  LGPPen.Free;
  LGPPath1.Free;
  LGPPath2.Free;
  LGPBrush.Free;
  LGPRegion.Free;
end;

end.




02_FrameRgn 関数を使用してリージョンの枠線を描画


前項のコードで三日月形の画像をリージョンとして描画できることが確認できました.
Halbow 資料館のサンプルと同じようにリージョンに枠線を描画してみます.枠線は  FrameRgn で描画できますが,この関数の引数にはリージョンのハンドルが必要です.TGPRegion の GetHRGN 関数でリージョンのハンドルが取得できます.


図4
実行結果
  • 線の太さは 5

リスト3
FrameRgn 関数を使用してリージョンの枠線を描画
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  ExtCtrls, StdCtrls;

type
  TForm1 = class(TForm)
    procedure FormPaint(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses
  GDIPAPI,
  GDIPOBJ;

{$R *.DFM}

//=============================================================================
//  フォームのOnPaintイベント処理
//=============================================================================
procedure TForm1.FormPaint(Sender: TObject);
var
  LLeft      : Single;
  LTop       : Single;
  LWidth     : Single;
  LHeight    : Single;
  LGPGraphic : TGPGraphics;
  LGPPath1   : TGPGraphicsPath;
  LGPPath2   : TGPGraphicsPath;
 LGPPen     : TGPPen;
  LGPBrush   : TGPSolidBrush;
  LGPRegion  : TGPRegion;
  LHrgn      : HRGN;
begin
  LGPGraphic := TGPGraphics.Create(Canvas.Handle);
  //背景を白で塗り潰す
  LGPGraphic.Clear(aclWhite);


  LLeft   := 40;
  LTop    := 30;
  LWidth  := 150;
  LHeight := 150;

  //パスのオブジェクトを生成
  LGPPath1 := TGPGraphicsPath.Create;
  LGPPath2 := TGPGraphicsPath.Create;

  //色と線の太さを指定してペンを生成
  LGPPen := TGPPen.Create(aclBlack, 5.0);
  //
  LGPBrush := TGPSolidBrush.Create(aclLightGreen);

  //円のパスを2つ生成
  LGPPath1.AddEllipse(LLeft,      LTop, LWidth, LHeight);
  LGPPath2.AddEllipse(LLeft + 50, LTop, LWidth, LHeight);

  //パス1の領域をリージョンとしてパス2の部分を除外する
  LGPRegion := TGPRegion.Create(LGPPath1);
  LGPRegion.Exclude(LGPPath2);

  //リージョンを塗り潰す
  LGPGraphic.FillRegion(LGPBrush, LGPRegion);

  //---------------------------------------------------------------------------
  //  FrameRgn関数わ使用してリージョンの枠線を描画する
  //  そのためにHRGN型のオブジェクトを取得しておく
  //---------------------------------------------------------------------------
  LHrgn := LGPRegion.GetHRGN(LGPGraphic);

  Canvas.Brush.Color := clBlack;
  FrameRgn(Canvas.Handle, LHrgn, Canvas.Brush.Handle, 5, 5);
  Canvas.Brush.Color := clWhite;

  DeleteObject(LHrgn);
  //---------------------------------------------------------------------------

  LGPGraphic.Free;
  LGPPen.Free;
  LGPPath1.Free;
  LGPPath2.Free;
  LGPBrush.Free;
  LGPRegion.Free;
end;

end.




03_SetClip メソッドによるリージョンのクリップ


パスの輪郭線を描画すればリージョンの枠線も描画されますが,この時,以下のコードのように,リージョンをクリップして,リージョンの外側は描画しないようにします.リージョン自体はいわゆる単なる「領域」ですから,後から描画する内容が自動的にクリップされるわけではありません.
リージョンでクリップすると,輪郭線は元の半分の幅で描画されることになります.リージョン以外の背景は元のままです.


図5
リージョンでクリップしてパスの輪郭線を描画
  • 実際の半分の太さで描画される

リスト4
リージョンでクリップしてパスの輪郭線を描画
  //リージョンを塗り潰す
  LGPGraphic.FillRegion(LGPBrush, LGPRegion);

  //リージョンでクリップ
  LGPGraphic.SetClip(LGPRegion);

  LGPGraphic.DrawPath(LGPPen, LGPPath1);
  LGPGraphic.DrawPath(LGPPen, LGPPath2);

  //リージョンのクリップを解除
  LGPGraphic.ExcludeClip(LGPRegion);


リージョンのクリップの機能を利用すると,画像もリージョンでクリップすることができます.下図のような画像は,ウェブ上のリージョンの記事でよく見かけます.


図6
画像をリージョンでクリップして描画

リスト5
リージョン (領域) でクリップして画像を描画する例
procedure TForm1.FormPaint(Sender: TObject);
var
  LLeft      : Single;
  LTop       : Single;
  LWidth     : Single;
  LHeight    : Single;
  LGPGraphic : TGPGraphics;
  LGPPath1   : TGPGraphicsPath;
  LGPRegion  : TGPRegion;
  LGPBitmap  : TGPBitmap;
begin
  LGPGraphic := TGPGraphics.Create(Canvas.Handle);
  //背景を白で塗り潰す
  LGPGraphic.Clear(aclWhite);


  LLeft   := 40;
  LTop    := 30;
  LWidth  := 150;
  LHeight := 150;

  //パスのオブジェクトを生成
  LGPPath1 := TGPGraphicsPath.Create;

  //円のパスを生成
  LGPPath1.AddEllipse(LLeft, LTop, LWidth, LHeight);
  //パスの領域をリージョンとする
  LGPRegion := TGPRegion.Create(LGPPath1);

  //リージョンでクリップ
  LGPGraphic.SetClip(LGPRegion);

  //画像を読み込んで描画
  LGPBitmap := TGPBitmap.Create('PenguinsB.jpg');
  LGPGraphic.DrawImage(LGPBitmap, 0, 0);

  //リージョンのクリップを解除
  LGPGraphic.ExcludeClip(LGPRegion);


  LGPGraphic.Free;
  LGPPath1.Free;
  LGPRegion.Free;
  LGPBitmap.Free;
end;


SetClip メソッドには TCombineMode 型の引数があります.この値を CombineModeExclude にすると下図のように,リージョン内だけを描画しないようになります.



図7
リージョン内を描画しないようにする




04_マウスカーソル位置のリージョンを IsVisible 関数で検出


TGPResion の IsVisible 関数を使用すると,マウスカーソル位置のリージョンを検出することができます.そのテストプログラムです.
このテストではパスで作成した 2 つの円を,各々別のリージョンにしています.

この関数を利用すると, マウスによるリージョンの移動や,TGraphicControl で CM_HITTEST メッセージを処理することによってマウスイベントの透過等の機能を実装できます.



図8
右のリージョン内にマウスカーソルを移動
図9
左のリージョンと右のリージョンが重なった部分

リスト6
マウスカーソル位置のリージョンを IsVisible 関数で検出
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  ExtCtrls, StdCtrls, GDIPAPI, GDIPOBJ;

type
  TForm1 = class(TForm)
    PaintBox1: TPaintBox;
    Label1: TLabel;
    procedure FormDestroy(Sender: TObject);
    procedure PaintBox1Paint(Sender: TObject);
    procedure PaintBox1MouseMove(Sender: TObject; Shift: TShiftState; X,
      Y: Integer);
  private
    { Private 宣言 }
    FGPResion1 : TGPRegion;
    FGPResion2 : TGPRegion;
public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

//=============================================================================
//  フォーム破棄時の処理
//=============================================================================
procedure TForm1.FormDestroy(Sender: TObject);
begin
  if Assigned(FGPResion1) then  FreeAndNil(FGPResion1);
  if Assigned(FGPResion2) then  FreeAndNil(FGPResion2);
end;

//=============================================================================
//  PaintBox1のOnMouseMoveイベント処理
//=============================================================================
procedure TForm1.PaintBox1MouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Integer);
var
  LText : string;
begin
  LText := '';

  //Form1上での座標値に変換
  X := PaintBox1.Left + X;
  Y := PaintBox1.Top + Y;

  if FGPResion1.IsVisible(MakePoint(X, Y)) then LText := LText + ' Region1';
  if FGPResion2.IsVisible(MakePoint(X, Y)) then LText := LText + ' Region2';

  Label1.Caption := LText;
end;

//=============================================================================
//  PaintBox1のOnPaintイベント処理
//=============================================================================
procedure TForm1.PaintBox1Paint(Sender: TObject);
var
  LLeft      : Single;
  LTop       : Single;
  LWidth     : Single;
  LHeight    : Single;
  LGPGraphic : TGPGraphics;
  LGPPath1   : TGPGraphicsPath;
  LGPPath2   : TGPGraphicsPath;
  LGPBrush   : TGPSolidBrush;
begin
  LGPGraphic := TGPGraphics.Create(Canvas.Handle);
  //背景を白で塗り潰す
  LGPGraphic.Clear(aclWhite);

  if Assigned(FGPResion1) then  FreeAndNil(FGPResion1);
  if Assigned(FGPResion2) then  FreeAndNil(FGPResion2);


  //Form1上での座標値に変換
  LLeft   := PaintBox1.Left + 40;
  LTop    := PaintBox1.Top + 20;
  LWidth  := 150;
  LHeight := 150;

  //パスのオブジェクトを生成
  LGPPath1 := TGPGraphicsPath.Create;
  LGPPath2 := TGPGraphicsPath.Create;

  //円のパスを2つ生成
  LGPPath1.AddEllipse(LLeft,      LTop, LWidth, LHeight);
  LGPPath2.AddEllipse(LLeft + 50, LTop, LWidth, LHeight);

  //各々の円を別のリージョンとする
  FGPResion1 := TGPRegion.Create(LGPPath1);
  FGPResion2 := TGPRegion.Create(LGPPath2);

  //塗り潰しのブラシを生成して領域を塗り潰す
  LGPBrush := TGPSolidBrush.Create(aclLightBlue);
  LGPGraphic.FillRegion(LGPBrush, FGPResion1);
  LGPBrush.SetColor(aclGreen);
  LGPGraphic.FillRegion(LGPBrush, FGPResion2);


  LGPGraphic.Free;
  LGPPath1.Free;
  LGPPath2.Free;
  LGPBrush.Free;
end;

end.




05_マウスカーソル位置のパスを IsVisible 関数で検出


TGPGraphicsPath の IsVisible 関数を使用すると,マウスカーソル位置のパスを検出することができます.また,IsOutlineVisible 関数では,マウスカーソル位置のパスの輪郭線を検出できます.その動作確認用のテストプログラムです.
このテストでは,2 つの円をパスで作成して描画しています.各々の円は独立しています.

これらの関数を利用すると,マウスでパスを移動したり,TGraphicControl で CM_HITTEST メッセージを処理することによってマウスイベントの透過等の機能を実装できます.



図10
左の円の内側にマウスカーソルがある場合
図11
左と右の円が重なる場所で,右の円周上にマウスカーソルがある場合
図12
右の円の円周上にマウスカーソルがある場合

リスト7
マウスカーソル位置のパスを IsVisible メソッドで検出
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  ExtCtrls, StdCtrls, GDIPAPI, GDIPOBJ;

type
  TForm1 = class(TForm)
    PaintBox1: TPaintBox;
    Label1: TLabel;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure PaintBox1Paint(Sender: TObject);
    procedure PaintBox1MouseMove(Sender: TObject; Shift: TShiftState; X,
      Y: Integer);
  private
    { Private 宣言 }
    FGPPath1 : TGPGraphicsPath;
    FGPPath2 : TGPGraphicsPath;
   FGPPen   :TGPPen;
public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

//=============================================================================
//  フォーム生成時の処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin

  //パスのオブジェクトを生成
  FGPPath1 := TGPGraphicsPath.Create;
  FGPPath2 := TGPGraphicsPath.Create;

  //色と線の太さを指定してペンを生成
  FGPPen := TGPPen.Create(aclBlack, 10.0);
end;

//=============================================================================
//  フォーム破棄時の処理
//=============================================================================
procedure TForm1.FormDestroy(Sender: TObject);
begin
  FreeAndNil(FGPPath1);
  FreeAndNil(FGPPath2);
  FreeAndNil(FGPPen);
end;

//=============================================================================
//  PaintBox1のOnMouseMoveイベント処理
//=============================================================================
procedure TForm1.PaintBox1MouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Integer);
var
  LText : string;
begin
  LText := '';

  //Form1上での座標値に変換
  X := PaintBox1.Left + X;
  Y := PaintBox1.Top + Y;

  if FGPPath1.IsVisible(X, Y) then LText := LText + ' Path1';
  if FGPPath2.IsVisible(X, Y) then LText := LText + ' Path2';

  if FGPPath1.IsOutlineVisible(X, Y, FGPPen) then LText := LText + ' [Path1 輪郭線上]';
  if FGPPath2.IsOutlineVisible(X, Y, FGPPen) then LText := LText + ' [Path2 輪郭線上]';

  Label1.Caption := LText;
end;

//=============================================================================
//  PaintBox1のOnPaintイベント処理
//=============================================================================
procedure TForm1.PaintBox1Paint(Sender: TObject);
var
  LLeft      : Single;
  LTop       : Single;
  LWidth     : Single;
  LHeight    : Single;
  LGPGraphic : TGPGraphics;
begin
  LGPGraphic := TGPGraphics.Create(Canvas.Handle);
  //背景を白で塗り潰す
  LGPGraphic.Clear(aclWhite);


  //Form1上での座標値に変換
  LLeft   := PaintBox1.Left + 40;
  LTop    := PaintBox1.Top + 20;
  LWidth  := 150;
  LHeight := 150;

  //円のパスを2つ生成
  FGPPath1.AddEllipse(LLeft,      LTop, LWidth, LHeight);
  FGPPath2.AddEllipse(LLeft + 50, LTop, LWidth, LHeight);

  //パスを描画
  LGPGraphic.DrawPath(FGPPen, FGPPath1);
  LGPGraphic.DrawPath(FGPPen, FGPPath2);

  LGPGraphic.Free;
end;

end.