Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2017年05月28日] [更新 2017年05月28日] GDI+ 関係サンプル
   
GDI+ 関係サンプル 
G018_不定形な曲線の描画
動作確認等 Windows 7 U64(SP1) + Delphi XE(UP1) Pro
G018_GDIPlus_ClosedOpenCurve.zip [2,349 KB] 2017年05月28日版 (EXE 同梱)







GDI+ による不定形な曲線の描画


GDI+ の関数を使用した不定形な曲線を描画するサンプルです.主にパスを生成して,TGPGraphics のDrawPath 関数でそのパスを描画するようにしています.パスの座標値をマウスで操作して図形を変形できるようにしてみました.

サンプルでは使用する座標値のデータを静的配列で設定しています.動的配列を使用する場合は以下のコードを参考にしてください.一般的には動的配列の方が使いやすいと思われます.



リスト1
動的配列の定義とデータのセット (F は実数型で使用する型等を意味する)
//-----------------------------------------------------------------------------
//  動的配列変数の定義例
//  以下のいずれかが使用できる
//  TArrayの使用にはusesにCollectionsが必要
//-----------------------------------------------------------------------------

var
  //整数型(Integer型)の座標値の動的配列の定義
  FPosArray : array of TGPPoint;
  FPosArray : TPointDynArray;
  FPosArray : TArray<TGPPoint>;

  //実数型(Single型)の座標値の動的配列の定義
  FPosArray : array of TGPPointF;
  FPosArray : TPointFDynArray;
  FPosArray : TArray<TGPPointF>;

//-----------------------------------------------------------------------------
//  動的配列の要素への座標値の代入例
//-----------------------------------------------------------------------------

  //整数型(Integer型)の座標値の場合
  SetLength(FPosArray, 4);
  FPosArray[0] := MakePoint(150,  50);
  FPosArray[1] := MakePoint(250, 150);
  FPosArray[2] := MakePoint(150, 250);
  FPosArray[3] := MakePoint( 50, 150);

  //実数型(Single型)の座標値の場合(小数点以下も指定して実数であることを明示する)
  SetLength(FPosArray, 4);
  FPosArray[0] := MakePoint(150.0,  50.0);
  FPosArray[1] := MakePoint(250.0, 150.0);
  FPosArray[2] := MakePoint(150.0, 250.0);
  FPosArray[3] := MakePoint( 50.0, 150.0);

//-----------------------------------------------------------------------------
//  TGPGraphics.DrawCurve
//  TGPGraphics.DrawClosedCurve
//  TGPGraphicsPath.AddCurve
//  TGPGraphicsPath.AddClosedCurve
//  TGPGraphics.DrawBeziers
//  TGPGraphicsPath.AddBeziers
//
//  等の配列の引数はPGPPointでキャストする
//  動的配列は最初の要素のポインタとなっているので@(Addr)を使用しなくてよい
//-----------------------------------------------------------------------------

  TGPGraphicsPath.AddClosedCurve(PGPPoint(FPosArray), Length(FPosArray));


[備考]
座標値が与えられている場合,曲線を描くのにパス (TGPGraphicsPath) を使う必然性はありません.マウス操作で図形の座標値を移動するのにパスを使用する必然性もありません.
パスの利点は,直線,四角形等の図形,あるいは本ページで扱っている曲線や文字列を結合して 1 つの図形として扱えるようにできることです.もちろん他にも利点があります.GDI+ の他の記事やサンプルで確認してください.





01_DrawClosedCurve または AddClosedCurve で閉じた曲線を描画


パスを生成する TGPGraphicsPath の AddClosedCurve メソッドを使用したサンプルです.閉じた曲線を作成します.このメソッドでは曲線の程度を引数で指定できます.0 の時は座標値を結ぶ直線となります.省略した時のデフォルトの値は 0.5 です.
パスを生成する必要がない場合は,TGPGraphics の DrawClosedCurve メソッドで描画します.


図1
設計時の画面
  • TSpinEdit は曲線の強さの程度を指定する値の設定用
  • この値の 1/100 の値を使用する
図2
実行開始時の結果
図3
曲線の強さが 0 の時
図4
曲線の強さの値が 0.80 の時
図5
マウスでいろいろ変形

リスト2
TGPGraphicsPath の AddClosedCurve メソッドを使用して閉じた曲線を描画
unit Unit1;

interface

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

type
  TMyPanel = class(TPanel)
  private
    FCurveTension : Single;
    procedure SetFCurveTension(const Value: Single);
  protected
    FPointNumber : Integer;
    FMoveFlag    : Boolean;
    FGrabSize    : Integer;
    procedure Paint; override;
    procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X: Integer;
      Y: Integer); override;
    procedure MouseMove(Shift: TShiftState; X: Integer; Y: Integer); override;
    procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X: Integer;
      Y: Integer); override;
    procedure WMEraseBkgnd(var Message: TWMEraseBkgnd); message WM_ERASEBKGND;
  public
    constructor Create(AOwner: TComponent); override;
    property CurveTension : Single read FCurveTension write SetFCurveTension;
  end;

  TForm1 = class(TForm)
    SpinEdit1: TSpinEdit;
    procedure FormCreate(Sender: TObject);
    procedure SpinEdit1Change(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
    MyPanel : TMyPanel;
  end;

var
  Form1: TForm1;

implementation

uses
  GDIPOBJ,
  GDIPAPI,
  Types;

{$R *.DFM}

var
  //テスト用のデータ配列
  FPosArray : array[0..3] of TGPPoint = ((X:150; Y:50),
                                         (X:250; Y:150),
                                         (X:150; Y:250),
                                         (X: 50; Y:150));

//=============================================================================
//  フォーム生成時の処理
//
//  Form1のDoubleBufferedをTrueにしている
//  OnCreateでの設定とオブジェクトインスペクタでの設定では効果が違うので注意
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  Caption := 'AddClosedCurve による閉じた曲線';

  SpinEdit1.MinValue := 0;
  SpinEdit1.MaxValue := 250;

  //TMyPanelを生成して配置
  MyPanel := TMyPanel.Create(Self);
  with MyPanel do begin
    Parent  := Self;
    Left    := 5;
    Top     := 35;
    Width   := Self.ClientWidth  - 10;
    Height  := Self.ClientHeight - Top -10;
    Anchors := [akLeft, akTop, akRight, akBottom];
  end;

  SpinEdit1.Value := Trunc(MyPanel.CurveTension * 100);
end;

//=============================================================================
//  SpinEdit1のOnChangeイベント
//  TMyPanelのCurveTensionプロパティの設定用
//=============================================================================
procedure TForm1.SpinEdit1Change(Sender: TObject);
begin
  MyPanel.CurveTension := SpinEdit1.Value / 100;
end;

{ TMyPanel }

//-----------------------------------------------------------------------------
//  TMyPanelのコンストラクタ
//  変数の初期設定
//-----------------------------------------------------------------------------
constructor TMyPanel.Create(AOwner: TComponent);
begin
  inherited;

  FCurveTension := 0.5;
  FPointNumber  := -1;
  FMoveFlag     := False;
  FGrabSize     := 4;
end;

//-----------------------------------------------------------------------------
//  MouseDown メソッド
//-----------------------------------------------------------------------------
procedure TMyPanel.MouseDown(Button: TMouseButton; Shift: TShiftState; X,
  Y: Integer);
var
  LRect : TRect;
  i     : Integer;
begin
  inherited;

  if Button = mbLeft then begin
    FPointNumber := -1;
    for i := 0 to Length(FPosArray) - 1 do begin
      LRect := Rect(FPosArray[i].X - FGrabSize - 1,
                    FPosArray[i].Y - FGrabSize - 1,
                    FPosArray[i].X + FGrabSize + 1,
                    FPosArray[i].Y + FGrabSize + 1);
      if PtInRect(LRect, Point(X, Y)) then begin
        FPointNumber := i;
        FMoveFlag := True;

        //マウスカーソルの移動範囲を,このTMyPanel内だけとする
        GetWindowRect(Handle, LRect);
        ClipCursor(@LRect);

        break;
      end;
    end;
  end;
end;

//-----------------------------------------------------------------------------
//  MouseMove メソッド
//-----------------------------------------------------------------------------
procedure TMyPanel.MouseMove(Shift: TShiftState; X, Y: Integer);
begin
  inherited;

  if FMoveFlag then begin
    //マウスボタンが押下状態の時はその端点の位置の値を更新する
    //FPosintNumberは押されている座標値の番号
    if FPointNumber = -1 then exit;
    FPosArray[FPointNumber].X := X;
    FPosArray[FPointNumber].Y := Y;
    Repaint;
  end;
end;

//-----------------------------------------------------------------------------
//  MouseUp メソッド
//-----------------------------------------------------------------------------
procedure TMyPanel.MouseUp(Button: TMouseButton; Shift: TShiftState; X,
  Y: Integer);
begin
  inherited;

  FMoveFlag := False;
  //マウスカーソルのクリップを解除
  ClipCursor(nil);
end;

//-----------------------------------------------------------------------------
//  CurveTensionプロパティの設定用メソッド
//  CurveTensionは線を曲げる強さ.0の時は座標値を結ぶ直線になる
//-----------------------------------------------------------------------------
procedure TMyPanel.SetFCurveTension(const Value: Single);
begin
  if FCurveTension <> Value then begin
    FCurveTension := Value;
    Invalidate;
  end;
end;

//-----------------------------------------------------------------------------
//  TMyPanelのPaint メソッド
//  配列の座標値に基づいて曲線を描画する
//-----------------------------------------------------------------------------
procedure TMyPanel.Paint;
var
  LGPGraphic : TGPGraphics;
  LGPPath    : TGPGraphicsPath;
  LGPPen     : TGPPen;
  LGPBrush   : TGPSolidBrush;
  LGPRect    : TGPRect;
  i         : Integer;
begin
  inherited;

  LGPGraphic := TGPGraphics.Create(Canvas.Handle);
  //WM_ERASEBKGNDで背景の再描画を無効にしたのでその対策(背景を指定色で塗り潰す)
  LGPGraphic.Clear(aclWhite);


  LGPPen   := TGPPen.Create(aclRed, 5);
  LGPBrush := TGPSolidBrush.Create(aclBlack);

  //TGPGraphicsPathを生成して図形バスを追加
  LGPPath := TGPGraphicsPath.Create;
  LGPPath.AddClosedCurve(PGPPoint(@FPosArray[0]), Length(FPosArray), FCurveTension);

  //TGPGraphicsでパスを描画する
  LGPGraphic.DrawPath(LGPPen, LGPPath);


  //パスの座標位置にグラブハンドルを描画
  LGPPen.SetColor(aclBlack);
  LGPPen.SetWidth(2.5);
  for i := 0 to Length(FPosArray) - 1  do begin
    LGPRect := MakeRect(Rect(FPosArray[i].X - FGrabSize,
                             FPosArray[i].Y - FGrabSize,
                             FPosArray[i].X + FGrabSize,
                             FPosArray[i].Y + FGrabSize));
    LGPGraphic.DrawRectangle(LGPPen, LGPRect);
    LGPGraphic.FillRectangle(LGPBrush, LGPRect);
  end;

  LGPGraphic.Free;
  LGPPen.Free;
  LGPBrush.Free;
  LGPPath.Free;
end;

//-----------------------------------------------------------------------------
//  WM_ERASEBKGNDメッセージ処理
//  チラつき防止対策.何もしない
//-----------------------------------------------------------------------------
procedure TMyPanel.WMEraseBkgnd(var Message: TWMEraseBkgnd);
begin
//
end;

end.


マウスによる曲線の編集が必要なければ,例えば以下のようなコードになります.パスの内部をハッチブラシで塗り潰してみました.



図6
実行結果

リスト3
マウスの操作が必要ない場合はグラブハンドル等の描画コードは不要
unit Unit1;

interface

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

type
  TMyPanel = class(TPanel)
  protected
    procedure Paint; override;
    procedure WMEraseBkgnd(var Message: TWMEraseBkgnd); message WM_ERASEBKGND;
  end;

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

var
  Form1: TForm1;

implementation

uses
  GDIPOBJ,
  GDIPAPI;

{$R *.DFM}

var
  FPosArray : array[0..3] of TGPPoint = ((X:150; Y:50),
                                         (X:250; Y:150),
                                         (X:150; Y:250),
                                         (X: 50; Y:150));

//=============================================================================
//  フォーム生成時の処理
//
//  Form1のDoubleBufferedをTrueにしている
//  OnCreateでの設定とオブジェクトインスペクタでの設定では効果が違うので注意
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  Caption := 'AddClosedCurve による閉じた曲線';

  //TMyPanelを生成して配置
  MyPanel := TMyPanel.Create(Self);
  with MyPanel do begin
    Parent := Self;
    Align  := alClient;
  end;
end;

{ TMyPanel }

//-----------------------------------------------------------------------------
//  TMyPanelのPaint メソッド
//  座標値の配列の値に基づいて曲線を描画する
//-----------------------------------------------------------------------------
procedure TMyPanel.Paint;
var
  LGPGraphic : TGPGraphics;
  LGPPath    : TGPGraphicsPath;
  LGPPen     : TGPPen;
  LGPBrush   : TGPHatchBrush;
begin
  inherited;

  LGPGraphic := TGPGraphics.Create(Canvas.Handle);
  LGPGraphic.Clear(aclWhite);

  LGPPen   := TGPPen.Create(aclGreen, 5);
  LGPBrush := TGPHatchBrush.Create(HatchStyleDottedGrid, aclBlue, aclWhite);

  LGPPath := TGPGraphicsPath.Create;
  LGPPath.AddClosedCurve(PGPPoint(@FPosArray[0]), Length(FPosArray));

  LGPGraphic.FillPath(LGPBrush, LGPPath);
  LGPGraphic.DrawPath(LGPPen, LGPPath);


  LGPGraphic.Free;
  LGPBrush.Free;
  LGPPath.Free;
  LGPPen.Free;
end;

//-----------------------------------------------------------------------------
//  WM_ERASEBKGNDメッセージ処理
//  チラつき防止対策.何もしない
//-----------------------------------------------------------------------------
procedure TMyPanel.WMEraseBkgnd(var Message: TWMEraseBkgnd);
begin
//
end;

end.


パスを生成する必要がなければ以下のように TGPGraphics の DrawClosedCurve メソッドで描画できます.これも図形の内部をハッチブラシで塗り潰してみました.



図7
実行結果

リスト4
TGPGraphics の DrawClosedCurve メソッドを使用して閉じた曲線を描画
procedure TMyPanel.Paint;
var
  LGPGraphic : TGPGraphics;
  LGPPen     : TGPPen;
  LGPBrush   : TGPHatchBrush;
begin
  inherited;

  LGPGraphic := TGPGraphics.Create(Canvas.Handle);
  LGPGraphic.Clear(aclWhite);

  LGPPen   := TGPPen.Create(aclLightBlue, 5);
  LGPBrush := TGPHatchBrush.Create(HatchStyle20Percent, aclDarkGreen, aclWhite);

  LGPGraphic.FillClosedCurve(LGPBrush, PGPPoint(@FPosArray[0]), Length(FPosArray));
  LGPGraphic.DrawClosedCurve(LGPPen, PGPPoint(@FPosArray[0]), Length(FPosArray));


  LGPGraphic.Free;
  LGPBrush.Free;
  LGPPen.Free;
end;


フォームの TCanas に直接閉じた曲線を描画する例です.



図8
実行結果

リスト5
フォームに直接描画する例
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
  GDIPOBJ,
  GDIPAPI;

{$R *.DFM}

var
  FPosArray : array[0..3] of TGPPoint = ((X:150; Y:50),
                                         (X:250; Y:150),
                                         (X:150; Y:250),
                                         (X: 50; Y:150));

//=============================================================================
//  Form1のOnPaintイベント
//=============================================================================
procedure TForm1.FormPaint(Sender: TObject);
var
  LGPGraphic : TGPGraphics;
  LGPPen     : TGPPen;
begin
  LGPGraphic := TGPGraphics.Create(Canvas.Handle);
  LGPGraphic.Clear(aclWhite);

  LGPPen := TGPPen.Create(aclBlack, 5);
  LGPGraphic.DrawClosedCurve(LGPPen, PGPPoint(@FPosArray[0]), Length(FPosArray));

  LGPGraphic.Free;
  LGPPen.Free;
end;

end.




02_DrawCurve または AddCurve で開いた曲線を描画


パスを生成する TGPGraphicsPath の AddCurve メソッドを使用したサンプルです.開いた曲線を作成します.このメソッドでは曲線の程度を引数で指定できます.0 の時は座標値を結ぶ直線となります.省略した時のデフォルトの値は 0.5 です.
パスを生成する必要がない場合は,TGPGraphics の DrawCurve メソッドで描画します.



図9
実行開始時の画面
  • TSpinEdit は曲線の強さの程度を指定する値の設定用
  • この値の 1/100 の値を使用する
図10
曲線の強さが 0 の時
図11
マウスでいろいろ変形

リスト6
TGPGraphicsPath の AddCurve メソッドを使用して開いた曲線を描画
unit Unit1;

interface

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

type
  TMyPanel = class(TPanel)
  private
    FCurveTension : Single;
    procedure SetFCurveTension(const Value: Single);
  protected
    FPointNumber : Integer;
    FMoveFlag    : Boolean;
    FGrabSize    : Integer;
    procedure Paint; override;
    procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X: Integer;
      Y: Integer); override;
    procedure MouseMove(Shift: TShiftState; X: Integer; Y: Integer); override;
    procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X: Integer;
      Y: Integer); override;
    procedure WMEraseBkgnd(var Message: TWMEraseBkgnd); message WM_ERASEBKGND;
  public
    constructor Create(AOwner: TComponent); override;
    property CurveTension : Single read FCurveTension write SetFCurveTension;
  end;

  TForm1 = class(TForm)
    SpinEdit1: TSpinEdit;
    procedure FormCreate(Sender: TObject);
    procedure SpinEdit1Change(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
    MyPanel : TMyPanel;
  end;

var
  Form1: TForm1;

implementation

uses
  GDIPOBJ,
  GDIPAPI,
  Types;

{$R *.DFM}

var
  FPosArray : array[0..3] of TGPPoint = ((X:50;  Y:120),
                                         (X:120; Y: 30),
                                         (X:170; Y:150),
                                         (X:250; Y:100));

//=============================================================================
//  フォーム生成時の処理
//
//  Form1のDoubleBufferedをTrueにしている
//  OnCreateでの設定とオブジェクトインスペクタでの設定では効果が違うので注意
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  Caption := 'AddCurve による開いた曲線';

  SpinEdit1.MinValue := 0;
  SpinEdit1.MaxValue := 250;

  //TMyPanelを生成して配置
  MyPanel := TMyPanel.Create(Self);
  with MyPanel do begin
    Parent  := Self;
    Left    := 5;
    Top     := 35;
    Width   := Self.ClientWidth  - 10;
    Height  := Self.ClientHeight - Top - 10;
    Anchors := [akLeft, akTop, akRight, akBottom];
  end;

  SpinEdit1.Value := Trunc(MyPanel.CurveTension * 100);
end;

//=============================================================================
//  SpinEdit1のOnChangeイベント
//  TMyPanelのCurveTensionプロパティの設定用
//=============================================================================
procedure TForm1.SpinEdit1Change(Sender: TObject);
begin
  MyPanel.CurveTension := SpinEdit1.Value / 100;
end;

{ TMyPanel }

//-----------------------------------------------------------------------------
//  TMyPanelのコンストラクタ
//  変数の初期設定
//-----------------------------------------------------------------------------
constructor TMyPanel.Create(AOwner: TComponent);
begin
  inherited;

  FCurveTension := 0.5;
  FPointNumber  := -1;
  FMoveFlag     := False;
  FGrabSize     := 4;
end;

//-----------------------------------------------------------------------------
//  MouseDown メソッド
//-----------------------------------------------------------------------------
procedure TMyPanel.MouseDown(Button: TMouseButton; Shift: TShiftState; X,
  Y: Integer);
var
  LRect : TRect;
  i     : Integer;
begin
  inherited;

  if Button = mbLeft then begin
    FPointNumber := -1;
    for i := 0 to Length(FPosArray) - 1 do begin
      LRect := Rect(FPosArray[i].X - FGrabSize - 1,
                    FPosArray[i].Y - FGrabSize - 1,
                    FPosArray[i].X + FGrabSize + 1,
                    FPosArray[i].Y + FGrabSize + 1);
      if PtInRect(LRect, Point(X, Y)) then begin
        FPointNumber := i;
        FMoveFlag := True;

        //マウスカーソルの移動範囲を,このTMyPanel内だけとする
        GetWindowRect(Handle, LRect);
        ClipCursor(@LRect);

        break;
      end;
    end;
  end;
end;

//-----------------------------------------------------------------------------
//  MouseMove メソッド
//-----------------------------------------------------------------------------
procedure TMyPanel.MouseMove(Shift: TShiftState; X, Y: Integer);
var
  ARect : TRect;
  i     : Integer;
begin
  inherited;

  if FMoveFlag then begin
    if FPointNumber = -1 then exit;
    FPosArray[FPointNumber].X := X;
    FPosArray[FPointNumber].Y := Y;
    Repaint;
  end;
end;

//-----------------------------------------------------------------------------
//  MouseUp メソッド
//-----------------------------------------------------------------------------
procedure TMyPanel.MouseUp(Button: TMouseButton; Shift: TShiftState; X,
  Y: Integer);
begin
  inherited;

  FMoveFlag := False;
  //マウスカーソルのクリップを解除
  ClipCursor(nil);
end;

//-----------------------------------------------------------------------------
//  CurveTensionプロパティの設定用メソッド
//  CurveTensionは線を曲げる強さ.0の時は座標値を結ぶ直線になる
//-----------------------------------------------------------------------------
procedure TMyPanel.SetFCurveTension(const Value: Single);
begin
  if FCurveTension <> Value then begin
    FCurveTension := Value;
    Invalidate;
  end;
end;

//-----------------------------------------------------------------------------
//  TMyPanelのPaint メソッド
//  配列の座標値に基づいて曲線を描画する
//-----------------------------------------------------------------------------
procedure TMyPanel.Paint;
var
  LGPGraphic : TGPGraphics;
  LGPPath    : TGPGraphicsPath;
  LGPPen     : TGPPen;
  LGPBrush   : TGPSolidBrush;
  LGPRect    : TGPRect;
  i          : Integer;
begin
  inherited;

  LGPGraphic := TGPGraphics.Create(Canvas.Handle);
  //WM_ERASEBKGNDで背景の再描画を無効にしたのでその対策(背景を指定色で塗り潰す)
  LGPGraphic.Clear(aclWhite);


  LGPPen   := TGPPen.Create(aclRed, 5);
  LGPBrush := TGPSolidBrush.Create(aclBlack);

  //TGPGraphicsPathを生成して図形バスを追加
  LGPPath := TGPGraphicsPath.Create;
  LGPPath.AddCurve(PGPPoint(@FPosArray[0]), Length(FPosArray), FCurveTension);

  //TGPGraphicsでパスを描画する
  LGPGraphic.DrawPath(LGPPen, LGPPath);


  //パスの座標位置にグラブハンドルを描画
  LGPPen.SetWidth(2.5);
  for i := 0 to 3  do begin
    LGPRect := MakeRect(Rect(FPosArray[i].X - FGrabSize,
                             FPosArray[i].Y - FGrabSize,
                             FPosArray[i].X + FGrabSize,
                             FPosArray[i].Y + FGrabSize));
    LGPGraphic.DrawRectangle(LGPPen, LGPRect);
    LGPGraphic.FillRectangle(LGPBrush, LGPRect);
  end;

  LGPGraphic.Free;
  LGPPen.Free;
  LGPBrush.Free;
  LGPPath.Free;
end;

//-----------------------------------------------------------------------------
//  WM_ERASEBKGNDメッセージ処理
//  チラつき防止対策.何もしない
//-----------------------------------------------------------------------------
procedure TMyPanel.WMEraseBkgnd(var Message: TWMEraseBkgnd);
begin
//
end;

end.




03_DrawBezier または AddBezier でベジェ曲線を描画


TGPGraphics の DrawBezier メソッドを使用したサンプルです.ベジェ曲線を描画します.
パスを生成する場合は, TGPGraphicsPath の AddBezier メソッドてバスを作成し,TGPGraphics のDrawPath 関数でそのパスを描画します.

本サンプルは最少構成のベジエ曲線です.4 つの点を使用して描画します.この曲線は 3 次式で表される曲線となっています.PostScript フォント等,コンピュータで描かれる多くの曲線がベジエ曲線を使用しています.
曲線上の座標点を端点あるいはアンカーポイントと呼んでいます.描画開始の座標は始点,終了位置の座標は終点です.それ以外の位置の座標を制御点と言うことがあります.
アンカーポイントと制御点の距離によって曲線の強さが変化します.曲線は,アンカーポイントと制御点を結ぶ直線が接線となるように描かれます.



[備考]
4 点を超えるベジェ曲線は,TGPGraphics の DrawBeziers, TGPGraphicsPath の AddBeziers (末尾が s) メソッドで作成できます.




図12
実行開始時の画面
図13
操作例

リスト7
TGPGraphics の DrawBezier メソッドを使用してベジエ曲線を描画
unit Unit1;

interface

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

type
  TMyPanel = class(TPanel)
  protected
    FPointNumber : Integer;
    FMoveFlag    : Boolean;
    FGrabSize    : Integer;
    procedure Paint; override;
    procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X: Integer;
      Y: Integer); override;
    procedure MouseMove(Shift: TShiftState; X: Integer; Y: Integer); override;
    procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X: Integer;
      Y: Integer); override;
    procedure WMEraseBkgnd(var Message: TWMEraseBkgnd); message WM_ERASEBKGND;
  public
    constructor Create(AOwner: TComponent); override;
  end;

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

var
  Form1: TForm1;

implementation

uses
  GDIPOBJ,
  GDIPAPI;

{$R *.DFM}

var
  FPosArray : array[0..3] of TGPPoint = ((X:50; Y:120),
                                         (X:120; Y: 30),
                                         (X:170; Y:150),
                                         (X:250; Y:100));

//=============================================================================
//  フォーム生成時の処理
//
//  Form1のDoubleBufferedをTrueにしている
//  OnCreateでの設定とオブジェクトインスペクタでの設定では効果が違うので注意
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  Caption := 'DrawBezier によるベジェ曲線';

  //TMyPanelを生成して配置
  MyPanel := TMyPanel.Create(Self);
  with MyPanel do begin
    Parent  := Self;
    Left    := 5;
    Top     := 5;
    Width   := Self.ClientWidth  - 10;
    Height  := Self.ClientHeight - 10;
    Anchors := [akLeft, akTop, akRight, akBottom];
  end;
end;

{ TMyPanel }

//-----------------------------------------------------------------------------
//  TMyPanelのコンストラクタ
//  変数の初期設定
//-----------------------------------------------------------------------------
constructor TMyPanel.Create(AOwner: TComponent);
begin
  inherited;

  FPointNumber := -1;
  FMoveFlag    := False;
  FGrabSize    := 4;
end;

//-----------------------------------------------------------------------------
//  MouseDown メソッド
//-----------------------------------------------------------------------------
procedure TMyPanel.MouseDown(Button: TMouseButton; Shift: TShiftState; X,
  Y: Integer);
var
  LRect : TRect;
  i     : Integer;
begin
  inherited;

  if Button = mbLeft then begin
    FPointNumber := -1;
    for i := 0 to Length(FPosArray) - 1 do begin
      LRect := Rect(FPosArray[i].X - FGrabSize - 1,
                    FPosArray[i].Y - FGrabSize - 1,
                    FPosArray[i].X + FGrabSize + 1,
                    FPosArray[i].Y + FGrabSize + 1);
      if PtInRect(LRect, Point(X, Y)) then begin
        FPointNumber := i;
        FMoveFlag := True;

        //マウスカーソルの移動範囲を,このTMyPanel内だけとする
        GetWindowRect(Handle, LRect);
        ClipCursor(@LRect);

        break;
      end;
    end;
  end;
end;

//-----------------------------------------------------------------------------
//  MouseMove メソッド
//-----------------------------------------------------------------------------
procedure TMyPanel.MouseMove(Shift: TShiftState; X, Y: Integer);
var
  ARect : TRect;
  i     : Integer;
begin
  inherited;

  if FMoveFlag then begin
    if FPointNumber = -1 then exit;
    FPosArray[FPointNumber].X := X;
    FPosArray[FPointNumber].Y := Y;

    Invalidate;
  end;
end;

//-----------------------------------------------------------------------------
//  MouseUp メソッド
//-----------------------------------------------------------------------------
procedure TMyPanel.MouseUp(Button: TMouseButton; Shift: TShiftState; X,
  Y: Integer);
begin
  inherited;

  FMoveFlag := False;
  //マウスカーソルのクリップを解除
  ClipCursor(nil);
end;

//-----------------------------------------------------------------------------
//  TMyPanelのPaint メソッド
//  グラブハンドルの座標に基づいてベジェ曲線を描画する
//-----------------------------------------------------------------------------
procedure TMyPanel.Paint;
var
  LGPGraphic : TGPGraphics;
  LGPPen     : TGPPen;
  LGPRect    : TGPRect;
  i          : Integer;
begin
  inherited;

  LGPGraphic := TGPGraphics.Create(Canvas.Handle);
  //WM_ERASEBKGNDで背景の再描画を無効にしたのでその対策(背景を指定色で塗り潰す)
  LGPGraphic.Clear(aclWhite);


  LGPPen := TGPPen.Create(aclRed, 5);
  //ベジェ曲線を描画
  LGPGraphic.DrawBezier(LGPPen, FPosArray[0], FPosArray[1], FPosArray[2], FPosArray[3]);

  //端点と制御点に矩形を描画
  LGPPen.SetColor(aclBlack);
  LGPPen.SetWidth(2.0);
  for i := 0 to Length(FPosArray) - 1 do begin
    LGPRect := MakeRect(Rect(FPosArray[i].X - FGrabSize, FPosArray[i].Y - FGrabSize,
                             FPosArray[i].X + FGrabSize, FPosArray[i].Y + FGrabSize));
    LGPGraphic.DrawRectangle(LGPPen, LGPRect);
  end;

  //端点の制御点の間に直線を描画
  LGPPen.SetColor(aclBlack);
  LGPPen.SetWidth(1.0);
  LGPGraphic.DrawLine(LGPPen, FPosArray[0].X,   FPosArray[0].Y,
                              FPosArray[1].X, FPosArray[1].Y);
  LGPGraphic.DrawLine(LGPPen, FPosArray[2].X,   FPosArray[2].Y,
                              FPosArray[3].X, FPosArray[3].Y);

  LGPGraphic.Free;
  LGPPen.Free;
end;

//-----------------------------------------------------------------------------
//  WM_ERASEBKGNDメッセージ処理
//  チラつき防止対策.何もしない
//-----------------------------------------------------------------------------
procedure TMyPanel.WMEraseBkgnd(var Message: TWMEraseBkgnd);
begin
//
end;

end.




04_ベクトルデータとしてのパスの座標値を取得


ビットマップのような画像は,ピクセルという座標単位で情報を保持しています.このような画像はラスタ形式の画像と呼んでいます.一方,本ページで扱っている図形は,座標値とその座標値の種類の情報を保持しています.GDI+ では,これらの情報は以下のような構造体の値として格納されます.
これらの情報を元に,各々の座標値を結ぶ座標値としてのピクセルの位置を計算して描画します.これを管理しているのがパス (グラフィックスパス) と呼ばれるものです.

type
  TPathData = packed class
  public
    Count  : Integer;
    Points : PGPPointF;
    Types  : PBYTE;
    constructor Create;
    destructor destroy; override;
  end;


パスの機能を使用すると,本ページでは単独で描画している図形類,直線,四角形,楕円,文字列等を組み合わせた図形を作成することができます.TGPGraphicsPath オブジェクトを生成してパスを追加しても描画されるわけではありません.パスとしての情報が格納されるだけです.TGPGraphics のパスの描画メソッド DrawPath( を実行すると,指定した描画先に描画します.
このように描かれた結果としての画像のことをベクタ形式のグラフィックスと呼んでいます.ビットマップのようなラスタ形式のものを画像と言い,ベクタ形式のものを図形と言うことがあります.

パスとして図形を描画しなくても,GDI+ はベクタ画像してして描画しますが,パスを生成する場合はそれらの操作が可能になります.

本サンプルは,矩形と円がどのような座標値を元描画しているかを確認します.GetPathData でパスのデータを取得して,パスの座標値位置にマークとしての円を描きます.



[備考]
ワードやエクセルでは,ラスタ形式のものを画像,ベクタ形式のものを図形としています.
Photoshop (フォトショップ) はビットマップ等のラスタ形式画像の加工処理用のアプリケーションです.Illustrator (イラストレータ) は,ベクタ形式で画像を作成したり処理するためのアプリケーションです.




図14
四角形の場合
図15
円の場合
  • ベジェ曲線として描かれている

リスト8
パスのデータを取得してその座標位置を表示
unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    procedure FormDestroy(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure FormPaint(Sender: TObject);
  private
    { Private 宣言 }
    FGPPath   : TGPGraphicsPath;
    FPathData : TPathData;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

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

//=============================================================================
//  矩形を生成してパスの座標値のデータを取得
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LGPRect : TGPRect;
begin
  if Assigned(FGPPath) then FreeAndNil(FGPPath);
  if Assigned(FPathData) then FreeAndNil(FPathData);

  FGPPath   := TGPGraphicsPath.Create;
  FPathData := TPathData.Create;

  LGPRect := MakeRect(30, 100, 170, 70);
  FGPPath.AddRectangle(LGPRect);

  //パスのデータを取得してTPathDateに格納
  FGPPath.GetPathData(FPathData);
  Invalidate;
end;

//=============================================================================
//  円を生成してパスの座標値のデータを取得
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LGPRect : TGPRect;
begin
  if Assigned(FGPPath) then FreeAndNil(FGPPath);
  if Assigned(FPathData) then FreeAndNil(FPathData);

  FGPPath   := TGPGraphicsPath.Create;
  FPathData := TPathData.Create;

  LGPRect := MakeRect(40, 60, 150, 150);
  FGPPath.AddEllipse(LGPRect);

  //パスのデータを取得してTPathDateに格納
  FGPPath.GetPathData(FPathData);
  Invalidate;
end;

//=============================================================================
//  Form1のOnPaintイベント
//=============================================================================
procedure TForm1.FormPaint(Sender: TObject);
var
  LGPGraphic : TGPGraphics;
  LGPPen     : TGPPen;
  LGPRect    : TGPRect;
  LSize      : Integer;
  LpPointsF  : PGPPointF;
  LPosX      : Integer;
  LPosY      : Integer;
  i          : Integer;
begin
  if not Assigned(FGPPath) then exit;

  LGPGraphic := TGPGraphics.Create(Canvas.Handle);
  LGPGraphic.Clear(aclWhite);

  //パスの情報で図形を描画
  LGPPen := TGPPen.Create(aclRed, 2);
  LGPGraphic.DrawPath(LGPPen, FGPPath);


  //パスの座標位置に円を描画する時のペンの設定と円のサイズ
  LGPPen.SetColor(aclBlack);
  LGPPen.SetWidth(2.5);
  LSize := 3;

  //パスのデータの先頭ポインタを取得
  LpPointsF := FPathData.Points;
  for i := 0 to FPathData.Count - 1  do begin
    //座標値を取得
    LPosX := Round(LpPointsf^.X);
    LPosY := Round(LpPointsf^.Y);

    LGPRect := MakeRect(Rect(LPosX - LSize, LPosY - LSize,
                             LPosX + LSize, LPosY + LSize));
    //パスの座標位置に円を描画
    LGPGraphic.DrawEllipse(LGPPen, LGPRect);

    //次のデータのポインタに移動
    Inc(LpPointsF);
  end;

  LGPGraphic.Free;
  LGPPen.Free;
end;

end.