Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2017年09月20日] [更新 2017年09月20日] 画像・図形の移動リサイズ関係サンプル
画像・図形の移動リサイズ関係サンプル 
R040_直線を描画するクラスの例
動作確認等 Windows 7 U64(SP1) + Delphi XE(UP1) Pro
R040_ResizeLine.zip [1,878 KB] 2017年09月20日版 (EXE 同梱)







直線を描画するクラスと描画領域の問題


矢印付きの直線を描画するコントロールのクラスです.

この直線描画用のクラスは特別な工夫をしています.
下図は基本クラスのページで説明した直線描画のコードで,線の幅を大きくした時の状態を示しています.直線の端点が描画コントロールでクリップされて描画が欠落しています.また,水平または垂直に近くなるほど線の幅が小さくなってしまいます.


図1
直線描画の問題
  • 端点が欠落してしまう
図2
垂直に近い状態にした場合
  • 線の幅が小さくなってしまう

この現象を回避するため,本来の描画コントロールとは別の少し大きい TGrapicControl を使用して,そこに実際の直線を描画するようにしています.
以下のプロパティを実装してます.追加のメソッドはありません.


プロパティ 説明
ExtendRect TRect 実際の描画領域の TRect の値.読み出し専用
ExtendLeft Integer 実際の描画領域の左端の X 座標値.読み出し専用
ExtendTop Integer 実際の描画領域の上端の Y 座標値.読み出し専用
ExtendRight Integer 実際の描画領域の右端の X 座標値.読み出し専用^
ExtendBottom Integer 実際の描画領域の下端の Y 座標値.読み出し専用
ExtendWidth Integer 実際の描画領域の幅.読み出し専用
ExtendHeight Integer 実際の描画領域の高さ.読み出し専用
LineType TplLineType 水平な直線,垂直な直線,斜めの直線の指定
初期値は ltNormal で斜めの直線
LineStartPos TPoint 描画開始の座標値.描画コントロールの生成時のみ有効な設定
指定しない場合は描画コントロールの左上の座標値
LineEndPos TPoint 描画終点の座標値.描画コントロールの生成時のみ有効な設定
指定しない場合は描画コントロールの右下の座標値
PenWidth Single 線幅.初期値は 3.0
PenColor TGPColor 線の色.GDI+ での色での指定なので注意.初期値は aclBlack (黒)
MiddleInset Single 矢印の底辺の切り込み深さ.線幅の倍数で指定.初期値は 0.0
ArrowWidth Single 矢印の幅.線幅の倍数で指定.初期値は 3.3
ArrowHeight Single 矢印の高さ.線幅の倍数で指定.初期値は 4.5
StartArrow Boolean 描画開始点の矢印描画の有無.初期値は False
EndArrow Boolean 描画終点の矢印描画の有無.初期値は False




01_直線描画用コントロールの動作確認


作成した直線描画コントロールのクラスのテストです.
直線の端点が欠落しないで描画できるこど確認できます.垂直に近い状態になっても線幅が保たれていることも確認できます.



図3
実行して直線を描画
図4
垂直に近い状態にした場合
図5
矢印付の直線にした場合

リスト1
直線描画用コントロールのテスト
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  ExtCtrls, StdCtrls, plBaseDragResize, plGPResizeLine, Spin, plContainerPanel,
  GDIPAPI, GDIPOBJ, GDIPUTIL;

type
  TForm1 = class(TForm)
    Panel1: TPanel;
    Button1: TButton;
    Button2: TButton;
    SpinEdit1: TSpinEdit;
    CheckBox1: TCheckBox;
    CheckBox2: TCheckBox;
    ColorDialog1: TColorDialog;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure SpinEdit1Change(Sender: TObject);
    procedure CheckBox1Click(Sender: TObject);
  private
    { Private 宣言 }
    FBackCtrl   : TplContainerPanel;
    FResizeLine : TplGPResizeLine;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

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

  //背景用のコントロール生成
  FBackCtrl := TplContainerPanel.Create(Self);
  FBackCtrl.Parent := Self;
  FBackCtrl.Align  := alClient;

  SpinEdit1.MinValue := 1;
  SpinEdit1.MaxValue := 200;
  SpinEdit1.Value    := SpinEdit1.MinValue;
end;

//=============================================================================
//  [生成]ボタン
//  GDI+ の色は ARGBのバイト順
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  FBackCtrl.DeleteAllCtrls;

  FResizeLine := TplGPResizeLine.Create(Self);
  FResizeLine.Parent      := FBackCtrl;
  FResizeLine.InverseDisp := True;
  FResizeLine.PenWidth    := 10.0;
  FResizeLine.PenColor    := $FF00A0FF;

  SpinEdit1.Value    := Trunc(FResizeLine.PenWidth);
  ColorDialog1.Color := ARGBToColorRef(FResizeLine.PenColor);
end;

//=============================================================================
//  線の色を変更
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
begin
  if not Assigned(FResizeLine) then exit;

  if ColorDialog1.Execute then begin
    FResizeLine.PenColor := ColorRefToARGB(ColorDialog1.Color);
  end;
end;

//=============================================================================
//  線の太さを変更
//=============================================================================
procedure TForm1.SpinEdit1Change(Sender: TObject);
begin
  if not Assigned(FResizeLine) then exit;

  FResizeLine.PenWidth := SpinEdit1.Value;
end;

//=============================================================================
//  矢印の描画有無の設定
//=============================================================================
procedure TForm1.CheckBox1Click(Sender: TObject);
begin
  if not Assigned(FResizeLine) then exit;

  FResizeLine.StartArrow := CheckBox1.Checked;
  FResizeLine.EndArrow   := CheckBox2.Checked;
  TCheckBox(Sender).Invalidate;
end;

end.




02_水平な直線と垂直な直線の描画


描画コントロールの高さを 0 にすれば水平な直線,幅を 0 にすれば垂直な直線になりますが,提供している描画コントロールでは幅または高さを 0 にできないようにしています.
幅または高さを 0 にしてしまうと,描画コントロールのサイズが 0 になってしまい,選択できなくなってしまいます.サイズが 0 の存在しないコントロールは選択できません.

そこで,水平な直線と垂直な直線はプロパティで指定するようにしました.以下の列挙型のいすれかの値で指定します.
下図はそれらの値を使用して実際に水平な,あるいは垂直な直線を描画するテストの結果です.


TplGPResizeLine.LineType: TplLineType
TplLineType = (ltNormal, ltHorz, ltVert)


図6
実行開始時は垂直な直線を描画
図7
斜めの直線に変更
  • 直線は対角線上に描画される
図8
水平な直線に変更
  • リサイズは水平方向だけ可能
図9
垂直な直線にして矢印を追加
  • リサイズは垂直方向だけ可能

リスト2
水平または垂直な直線を描画するテスト
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  ExtCtrls, StdCtrls, plBaseDragResize, plGPResizeLine, Spin, plContainerPanel,
  GDIPAPI, GDIPOBJ, GDIPUTIL;

type
  TForm1 = class(TForm)
    Panel1: TPanel;
    Button1: TButton;
    Button2: TButton;
    SpinEdit1: TSpinEdit;
    CheckBox1: TCheckBox;
    CheckBox2: TCheckBox;
    ColorDialog1: TColorDialog;
    Button3: TButton;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure SpinEdit1Change(Sender: TObject);
    procedure CheckBox1Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
  private
    { Private 宣言 }
    FBackCtrl   : TplContainerPanel;
    FResizeLine : TplGPResizeLine;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

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

  //背景用のコントロール生成
  FBackCtrl := TplContainerPanel.Create(Self);
  FBackCtrl.Parent := Self;
  FBackCtrl.Align  := alClient;

  SpinEdit1.MinValue := 1;
  SpinEdit1.MaxValue := 200;
  SpinEdit1.Value    := SpinEdit1.MinValue;
end;

//=============================================================================
//  [生成]ボタン
//  GDI+ の色は ARGBのバイト順
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  FBackCtrl.DeleteAllCtrls;

  FResizeLine := TplGPResizeLine.Create(Self);
  FResizeLine.Parent      := FBackCtrl;
  FResizeLine.InverseDisp := True;
  FResizeLine.PenWidth := 10.0;
  FResizeLine.PenColor := $FF00A0FF;

//  FResizeLine.LineStartPos := Point(250, 50);
//  FResizeLine.LineEndPos   := Point(100, 150);

  FResizeLine.LineType := ltVert;

  SpinEdit1.Value := Trunc(FResizeLine.PenWidth);
  ColorDialog1.Color := ARGBToColorRef(FResizeLine.PenColor);
end;

//=============================================================================
//  線の色を変更
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
begin
  if not Assigned(FResizeLine) then exit;

  if ColorDialog1.Execute then begin
    FResizeLine.PenColor := ColorRefToARGB(ColorDialog1.Color);
  end;
end;

//=============================================================================
//  線の太さを変更
//=============================================================================
procedure TForm1.SpinEdit1Change(Sender: TObject);
begin
  if not Assigned(FResizeLine) then exit;

  FResizeLine.PenWidth := SpinEdit1.Value;
end;

//=============================================================================
//  矢印の描画有無の設定
//=============================================================================
procedure TForm1.CheckBox1Click(Sender: TObject);
begin
  if not Assigned(FResizeLine) then exit;

  FResizeLine.StartArrow := CheckBox1.Checked;
  FResizeLine.EndArrow   := CheckBox2.Checked;
  TCheckBox(Sender).Update;
end;

//=============================================================================
//  LineTypeプロパティの設定
//  水平な直線,垂直な直線,斜めの直線の切り換え,変更
//=============================================================================
procedure TForm1.Button3Click(Sender: TObject);
var
  LWidth  : Integer;
  LHeight : Integer;
begin
  if not Assigned(FResizeLine) then exit;

  LWidth  := FResizeLine.Width;
  LHeight := FResizeLine.Height;

  if FResizeLine.LineType = ltNormal then begin
    FResizeLine.LineType := ltHorz;
  end else
  if FResizeLine.LineType = ltHorz then begin
    FResizeLine.LineType := ltVert;
    FResizeLine.Width    := LHeight;
    FResizeLine.Height   := LWidth;
  end else
  if FResizeLine.LineType = ltVert then begin
    FResizeLine.LineType := ltNormal;
    FResizeLine.Width    := LHeight;
    FResizeLine.Height   := LWidth;
  end;
end;

end.




03_描画を TBitmap で取得 - コントロールと実描画サイズ


描画の状態を TBitmap のビットマップ画像として取得するサンプルです.

まず画像描画用コントロールの記事で説明したように,DrawRoutine で TGPBitmap の画像オブジェクトを取得します.この時,あらかじめ TGPBitmap を作成しておきますが,このサイズは描画コントロールのサイズではなく,実際の描画内容の幅と高さである以下のプロパティを使用します.

TGPBitmap の画像が取得できたら GetHBITMAP メソッドでビットマップのハンドルを取得します.
描画内容がアルファチャンネル付きの画像の時は,この GetHBITMAP を使用すると正常に取得できないことがありますが,直線描画用のクラスで描画した内容であれば問題ありません.

このテストでは,取得した TBitmap を TImage に描画します.


TplGPResizeLine.ExtendWidth
TplGPResizeLine.ExtendHeight
TGPBitmap.GetHBITMAP


図10
描画内容をビットマップに変換
  • 右の TImage に表示しているのが取得したビットマップ画像

リスト3
描画内容を TBitmap で取得
//=============================================================================
//  TGPBitmapからTBitmapを作成
//
//  描画中のTGPBitmapを取得してTGPBitmap.GetHBITMAPメソッドで取得する方法
//
//  ExtendWidth, ExtendHeightは実際の描画領域の幅と高さ
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LCtrl     : TplGPResizeLine;
  LGPBitmap : TGPBitmap;
  LhBitmap  : HBITMAP;
  LBitmap   : TBitmap;
begin
  if FBackCtrl.CtrlList.Count = 0 then exit;

  Image1.Picture.Assign(nil);

  LCtrl := TplGPResizeLine(FBackCtrl.CtrlList[0]);

  LGPBitmap := TGPBitmap.Create(LCtrl.ExtendWidth, LCtrl.ExtendHeight);
  LCtrl.DrawRoutine(nil, LGPBitmap);

  LBitmap := TBitmap.Create;
  try
    LBitmap.AlphaFormat := afDefined;
    LGPBitmap.GetHBITMAP(0, LhBitmap);
    LBitmap.Handle := LhBitmap;

    Image1.Picture.Bitmap.Assign(LBitmap);
  finally
    FreeAndNil(LBitmap);
    FreeAndNil(LGPBitmap);
  end;
end;




04_直線描画用クラスのソースコード


矢印付きの直線描画用コントロールクラスのソースコードです.


リスト4
矢印付きの直線描画用コントロールクラスのソースコード
このユニットファイルをライブラリのパスが通った場所に置いて使用する
unit plGPResizeLine;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, plBaseDragResize,
  GDIPAPI, GDIPOBJ, GDIPUTIL;

type
  TplGPResizeLine = class;

  TplExtendResizeLine = class(TGraphicControl)
  private
    FLineCtrl : TplGPResizeLine;
  protected
    procedure CMHitTest(var Message: TCMHitTest); message CM_HITTEST;
    procedure Paint; override;
  public
    constructor Create(AOwner: TComponent); override;
  end;

  TplLineType = (ltNormal, ltHorz, ltVert);

  TplGPResizeLine = class(TplBaseDragResize)
  private
    FPenWidth     : Single;
    FPenColor     : TGPColor;
    FGPPath       : TGPGraphicsPath;
   FGPPen        : TGPPen;
    FExtendCtrl   : TplExtendResizeLine;
    FExtendHorz   : Integer;
    FExtendVert   : Integer;
    FLineStartPos : TPoint;
    FLineEndPos   : TPoint;
    FArrowWidth   : Single;
    FLineType     : TplLineType;
    FMiddleInset  : Single;
    FArrowHeight  : Single;
    FStartArrow   : Boolean;
    FEndArrow     : Boolean;
    FCanDrawFlag  : Boolean;
    function GetRegionFromGPBitmap(GDPBitmap: TGPBitmap; GDPRegion: TGPRegion):
      Boolean;
    function GetExtendGPbitmap(AGPBitmap: TGPBitmap): TGPBitmap;
    procedure SetFPenColor(const Value: TGPColor);
    procedure SetFPenWidth(const Value: Single);
    procedure SetDrawArrow(const Index: Integer; const Value: Boolean);
    function GetGPBitmap: TGPBitmap;
    function GetFExtendRect: TRect;
    procedure SetFArrowHeight(const Value: Single);
    procedure SetFArrowWidth(const Value: Single);
    function GetExtendPosSize(const Value: Integer): Integer;
    procedure SetFLineStartPos(const Value: TPoint);
    procedure SetFLineEndPos(const Value: TPoint);
    procedure SetFLineType(const Value: TplLineType);
    procedure SetFMiddleInset(const Value: Single);
  protected
    FGetFlag : Boolean;
    procedure SetParent(AParent: TWinControl); override;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure SetBounds(ALeft: Integer; ATop: Integer; AWidth: Integer;
      AHeight: Integer); override;
    procedure DrawRoutine(ACanvas: TCanvas; AGPBitmap: TGPBitmap = nil;
      AOffsetX: Integer = 0; AOffsetY: Integer = 0); override;
    property ExtendRect   : TRect    read GetFExtendRect;
    property ExtendLeft   : Integer  index 0 read GetExtendPosSize;
    property ExtendTop    : Integer  index 1 read GetExtendPosSize;
    property ExtendRight  : Integer  index 2 read GetExtendPosSize;
    property ExtendBottom : Integer  index 3 read GetExtendPosSize;
    property ExtendWidth  : Integer  index 4 read GetExtendPosSize;
    property ExtendHeight : Integer  index 5 read GetExtendPosSize;
    property LineType     : TplLineType read FLineType     write SetFLineType;
    property PenWidth     : Single      read FPenWidth     write SetFPenWidth;
    property PenColor     : TGPColor    read FPenColor     write SetFPenColor;
    property LineStartPos : TPoint      read FLineStartPos write SetFLineStartPos;
    property LineEndPos   : TPoint      read FLineEndPos   write SetFLineEndPos;
    property MiddleInset  : Single      read FMiddleInset  write SetFMiddleInset;
    property ArrowWidth   : Single      read FArrowWidth   write SetFArrowWidth;
    property ArrowHeight  : Single      read FArrowHeight  write SetFArrowHeight;
    property StartArrow   : Boolean  index 0 read FStartArrow write SetDrawArrow;
    property EndArrow     : Boolean  index 1 read FEndArrow   write SetDrawArrow;
  end;

implementation

uses
  Types,
  Math;

{ TplGPResizeLine }

//=============================================================================
//  コンストラクタ
//=============================================================================
constructor TplGPResizeLine.Create(AOwner: TComponent);
begin
  inherited;

  FPenWidth := 3.0;
  FPenColor := aclBlack;

  FLineStartPos := Point(Left, Top);
  FLineEndPos   := Point(Left + Width, Top + Height);
  FLineType     := ltNormal;

  FMiddleInset := 0;
  FArrowWidth  := 3.3;
  FArrowHeight := 4.5;
  FStartArrow  := False;
  FEndArrow    := False;

  FExtendHorz := 150;
  FExtendVert := 150;

  FCanDrawFlag := True;
end;

//=============================================================================
//  デコンストラクタ処理
//=============================================================================
destructor TplGPResizeLine.Destroy;
begin
  if Assigned(FGPPath) then FreeAndNil(FGPPath);
  if Assigned(FGPPen)  then FreeAndNil(FGPPen);

  inherited;
end;

//=============================================================================
//  SetParentメソッド処理
//=============================================================================
procedure TplGPResizeLine.SetParent(AParent: TWinControl);
begin
  inherited;

  if not Assigned(AParent) then exit;
  if Assigned(FExtendCtrl) then exit;

  FExtendCtrl := TplExtendResizeLine.Create(Self);
  FExtendCtrl.Parent := Self.Parent;
  BringToFront(FExtendCtrl);
end;

//=============================================================================
//  SetBoundsメソッド処理
//  水平直線または垂直直線の時は幅または高さを0にできないようにする
//=============================================================================
procedure TplGPResizeLine.SetBounds(ALeft, ATop, AWidth, AHeight: Integer);
begin
  if (AWidth <> 0) and (AHeight <> 0) then begin
    inherited;
  end else begin
    if (FLineType <> ltHorz) and (FLineType <> ltVert) then begin
      inherited;
    end;
  end;
end;

//-----------------------------------------------------------------------------
//  PenColorプロパティ設定用のメソッド
//-----------------------------------------------------------------------------
procedure TplGPResizeLine.SetFPenColor(const Value: TGPColor);
begin
  if FPenColor <> Value then begin
    FPenColor := Value;
    FExtendCtrl.Invalidate;
  end;
end;

//-----------------------------------------------------------------------------
//  PenWidthプロパティ設定用のメソッド
//-----------------------------------------------------------------------------
procedure TplGPResizeLine.SetFPenWidth(const Value: Single);
begin
  if Value < 1.0 then exit;

  if FPenWidth <> Value then begin
    FPenWidth := Value;

    if FLineType = ltHorz then begin
      if FStartArrow or FEndArrow then begin
        Height := Max(Trunc(FPenWidth) + 3, Trunc(FPenWidth * FArrowWidth) + 3);
      end else begin
        Height := Trunc(FPenWidth) + 3;
      end;
    end else
    if FLineType = ltVert then begin
      if FStartArrow or FEndArrow then begin
        Width := Max(Trunc(FPenWidth) + 3, Trunc(FPenWidth * FArrowWidth) + 3);
      end else begin
        Width := Trunc(FPenWidth) + 3;
      end;
    end;
    FExtendCtrl.Invalidate;
  end;
end;

//-----------------------------------------------------------------------------
//  LineStartPosプロパティの設定用メソッド
//  描画開始位置を設定
//  この描画コントロールの親であるコンテナコントロール上の位置の設定と同じ
//
//  このプロパティが有効なのは描画コントロールの生成時のみ
//-----------------------------------------------------------------------------
procedure TplGPResizeLine.SetFLineStartPos(const Value: TPoint);
var
  LLeft    : Integer;
  LLTop    : Integer;
  LLWidth  : Integer;
  LLHeight : Integer;
begin
  if Value.X <= 0 then exit;
  if Value.Y <= 0 then exit;

  if (FLineStartPos.X <> Value.X) or (FLineStartPos.Y <> Value.Y) then begin
    FLineStartPos := Value;

    LLeft := FLineStartPos.X;
    LLTop := FLineStartPos.Y;

    FLineType:= ltNormal;

    if FLineEndPos.X = 0 then FLineEndPos.X := FLineStartPos.X + Width;
    if FLineEndPos.Y = 0 then FLineEndPos.Y := FLineStartPos.Y + Height;

    LLWidth  := FLineEndPos.X - FLineStartPos.X;
    LLHeight := FLineEndPos.Y - FLineStartPos.Y;

    SetBounds(LLeft, LLTop, Abs(LLWidth), Abs(LLHeight));
  end;
end;

//-----------------------------------------------------------------------------
//  LineEndPosプロパティの設定用メソッド
//  描画終了位置を設定
//  この描画コントロールの親であるコンテナコントロール上の位置の設定と同じ
//
//  このプロパティが有効なのは描画コントロールの生成時のみ
//-----------------------------------------------------------------------------
procedure TplGPResizeLine.SetFLineEndPos(const Value: TPoint);
var
  LLeft    : Integer;
  LLTop    : Integer;
  LLWidth  : Integer;
  LLHeight : Integer;
begin
  if Value.X <= 0 then exit;
  if Value.Y <= 0 then exit;

  FLineType:= ltNormal;

  if (FLineEndPos.X <> Value.X) or (FLineEndPos.Y <> Value.Y) then begin
    FLineEndPos := Value;

    if FLineStartPos.X = 0 then FLineStartPos.X := FLineEndPos.X - Width;
    if FLineStartPos.Y = 0 then FLineStartPos.Y := FLineEndPos.Y - Height;

    LLeft := FLineStartPos.X;
    LLTop := FLineStartPos.Y;
    LLWidth  := FLineEndPos.X - FLineStartPos.X;
    LLHeight := FLineEndPos.Y - FLineStartPos.Y;

    SetBounds(LLeft, LLTop, Abs(LLWidth), Abs(LLHeight));
  end;
end;

//-----------------------------------------------------------------------------
//  FLineTypeプロパティの設定用メソッド
//  ltNormal 水平でも垂直でもない直線.斜めの直線
//  ltHorz   水平な直線
//  ltVert   垂直な直線
//-----------------------------------------------------------------------------
procedure TplGPResizeLine.SetFLineType(const Value: TplLineType);
begin
  if FLineType <> Value then begin
    FLineType := Value;
    if FLineType = ltHorz then begin
      if FStartArrow or FEndArrow then begin
        Height := Max(Trunc(FPenWidth) + 3, Trunc(FPenWidth * FArrowWidth) + 3);
      end else begin
        Height := Trunc(FPenWidth) + 3;
      end;
      GrabHorzOnly := True;
    end else
    if FLineType = ltVert then begin
      if FStartArrow or FEndArrow then begin
        Width := Max(Trunc(FPenWidth) + 3, Trunc(FPenWidth * FArrowWidth) + 3);
      end else begin
        Width := Trunc(FPenWidth) + 3;
      end;
      GrabVertOnly := True;
    end else begin
      GrabHorzOnly := False;
      GrabVertOnly := False;
    end;

    Invalidate;
    FExtendCtrl.Invalidate;
  end;
end;

//-----------------------------------------------------------------------------
//  MiddleInsetプロパティ(矢印のインセット)設定用メソッド
//  矢印の三角形の底辺の切り込み深さで線幅の倍数で指定
//-----------------------------------------------------------------------------
procedure TplGPResizeLine.SetFMiddleInset(const Value: Single);
begin
  if FMiddleInset <> Value then begin
    FMiddleInset := Value;
    FExtendCtrl.Invalidate;
  end;
end;

//-----------------------------------------------------------------------------
//  ArrowHeightプロパティ(矢印の高さ)設定用メソッド
//  矢印の三角形の高さで線幅の倍数で指定
//-----------------------------------------------------------------------------
procedure TplGPResizeLine.SetFArrowHeight(const Value: Single);
begin
  if FArrowHeight <> Value then begin
    FArrowHeight := Value;
    FExtendCtrl.Invalidate;
  end;
end;

//-----------------------------------------------------------------------------
//  ArrowWidthプロパティ(矢印幅)設定用メソッド
//  矢印の三角形底辺の長さで線幅の倍数で指定
//-----------------------------------------------------------------------------
procedure TplGPResizeLine.SetFArrowWidth(const Value: Single);
begin
  if FArrowWidth <> Value then begin
    FArrowWidth := Value;

    if FLineType = ltHorz then begin
      Height := Max(Trunc(FPenWidth) + 3, Trunc(FPenWidth * FArrowWidth) + 3);
    end else
    if FLineType = ltVert then begin
      Width := Max(Trunc(FPenWidth) + 3, Trunc(FPenWidth * FArrowWidth) + 3);
    end;
    FExtendCtrl.Invalidate;
  end;
end;

//-----------------------------------------------------------------------------
//  矢印の表示有無関係プロパティ設定用のメソッド
//-----------------------------------------------------------------------------
procedure TplGPResizeLine.SetDrawArrow(const Index: Integer;
  const Value: Boolean);
begin
  case Index of
  0:
    begin
      if FStartArrow <> Value then begin
        FStartArrow := Value;
      end;
    end;
  1:
    begin
      if FEndArrow <> Value then begin
        FEndArrow := Value;
      end;
    end;
  end;

  if FLineType = ltHorz then begin
    if FStartArrow or FEndArrow then begin
      Height := Max(Trunc(FPenWidth) + 3, Trunc(FPenWidth * FArrowWidth) + 3);
    end else begin
      Height := Trunc(FPenWidth) + 3;
    end;
  end else
  if FLineType = ltVert then begin
    if FStartArrow or FEndArrow then begin
      Width := Max(Trunc(FPenWidth) + 3, Trunc(FPenWidth * FArrowWidth) + 3);
    end else begin
      Width := Trunc(FPenWidth) + 3;
    end;
  end;
  FExtendCtrl.Invalidate;
end;

//-----------------------------------------------------------------------------
//  ExtendRectプロパティの取得用メソッド
//  実際に描画している範囲のTRect値を計算
//  描画のリージョンを取得してリージョンの位置とサイズから取得
//-----------------------------------------------------------------------------
function TplGPResizeLine.GetFExtendRect: TRect;
var
  LGPBitmap   : TGPBitmap;
  LGPImageRgn : TGPRegion;
  LGPGraphics : TGPGraphics;
  LRgnRect    : TGPRect;
begin
  Result := Bounds(0, 0, 0, 0);

  LGPBitmap := TGPBitmap.Create(FExtendCtrl.Width, FExtendCtrl.Height);
  FCanDrawFlag := False;
  DrawRoutine(nil, LGPBitmap);
  FCanDrawFlag := True;

  LGPImageRgn := TGPRegion.Create;
  try
    if GetRegionFromGPBitmap(LGPBitmap, LGPImageRgn) then begin
      LGPGraphics := TGPGraphics.Create(Canvas.Handle);
      LGPImageRgn.GetBounds(LRgnRect, LGPGraphics);
      Result := Bounds(FExtendCtrl.Left + LRgnRect.X, FExtendCtrl.Top + LRgnRect.Y,
                       LRgnRect.Width, LRgnRect.Height);

      FreeAndNil(LGPGraphics);
    end;
  finally
    FreeAndNil(LGPBitmap);
    FreeAndNil(LGPImageRgn);
  end;
end;

//-----------------------------------------------------------------------------
//  ExtendLeft等のプロパティ取得用関数
//  実際の描画開始位置とサイズを取得
//-----------------------------------------------------------------------------
function TplGPResizeLine.GetExtendPosSize(const Value: Integer): Integer;
var
  LRect : TRect;
begin
  LRect := GetFExtendRect;

  case Value of
    0: Result := LRect.Left;
    1: Result := LRect.Top;
    2: Result := LRect.Right;
    3: Result := LRect.Bottom;
    4: Result := LRect.Right - LRect.Left;
    5: Result := LRect.Bottom - LRect.Top;
  else
    Result := 0;
  end;
end;

//-----------------------------------------------------------------------------
//  引数のTGPBitmap(描画されているTGPBitmap)から実際の画像部分だけを取得
//  リージョンを作成してそのリージョンの位置から画像部分をコピーする
//-----------------------------------------------------------------------------
function TplGPResizeLine.GetExtendGPbitmap(AGPBitmap: TGPBitmap): TGPBitmap;
var
  LRetGPBitmap : TGPBitmap;
  LGPImageRgn  : TGPRegion;
  LGPGraphics  : TGPGraphics;
  LRgnRect     : TGPRect;
begin
  LGPImageRgn := TGPRegion.Create;
  try
    GetRegionFromGPBitmap(AGPBitmap, LGPImageRgn);
    LGPGraphics := TGPGraphics.Create(Canvas.Handle);
    LGPImageRgn.GetBounds(LRgnRect, LGPGraphics);

    FreeAndNil(LGPGraphics);

    LRetGPBitmap := TGPBitmap.Create(LRgnRect.Width, LRgnRect.Height);
    LGPGraphics  := TGPGraphics.Create(LRetGPBitmap);

    LGPGraphics.DrawImage(AGPBitmap,
                          MakeRect(0, 0, LRgnRect.Width, LRgnRect.Height),
                          LRgnRect.X,
                          LRgnRect.Y,
                          LRgnRect.Width,
                          LRgnRect.Height,
                          UnitPixel);
    Result := TGPBitmap(LRetGPBitmap.Clone);
  finally
    FreeAndNil(LGPGraphics);
    FreeAndNil(LGPImageRgn);
    FreeAndNil(AGPBitmap);
    FreeAndNil(LRetGPBitmap);
  end;
end;

//-----------------------------------------------------------------------------
//  描画内容をTGPBitmap画像として取得する時に使用する
//-----------------------------------------------------------------------------
function TplGPResizeLine.GetGPBitmap: TGPBitmap;
var
  LGPBitmap : TGPBitmap;
begin
  LGPBitmap := TGPBitmap.Create(FExtendCtrl.Width, FExtendCtrl.Height);
  FCanDrawFlag := False;
  DrawRoutine(nil, LGPBitmap);
  FCanDrawFlag := True;

  Result := GetExtendGPbitmap(LGPBitmap);
end;

//-----------------------------------------------------------------------------
//  リージョンの作成用関数
//  透明色以外のピクセルをリージョンにする
//  このリージョンを利用して実際の描画開始位置とサイズを取得する
//-----------------------------------------------------------------------------
function TplGPResizeLine.GetRegionFromGPBitmap(GDPBitmap: TGPBitmap;
  GDPRegion: TGPRegion): Boolean;
var
  LImgWidth   : Integer;
  LImgHeight  : Integer;
  LBmpData    : TBitmapData;
  LScanLine   : PByte;
  LpQuad      : PRGBQuad;
  LDataStride : Integer;
  LXPos       : integer;
  LYPos       : Integer;
  LAValue     : Byte;
  LRgnRect    : TGPRect;
  LInImage    : Boolean;
begin
  Result := False;
  GDPRegion.MakeEmpty;

  LImgWidth  := GDPBitmap.GetWidth;
  LImgHeight := GDPBitmap.GetHeight;

  //ビットマップとしてのデータを取得
  LRgnRect := MakeRect(0, 0, 0, 0);
  if GDPBitmap.LockBits(MakeRect(0, 0, LImgWidth, LImgHeight),ImageLockModeWrite,
                        PixelFormat32bppARGB, LBmpData) <> Ok then exit;

  LScanLine   := LBmpData.Scan0;
  LDataStride := LBmpData.Stride;

  LInImage := False;

  //リージョンの領域矩形(ピクセル数の初期値は0
  //透明色以外の時はそのピクセルを追加してUnionしていく
  LRgnRect := MakeRect(0, 0, 0, 0);

  for LYPos := 0 to LImgHeight - 1 do begin
    LpQuad := PRGBQuad(LScanLine);

    for LXPos := 0 to LImgWidth - 1 do begin
      LAValue    := LpQuad^.rgbReserved;

      if not LInImage then begin
        if LAValue <> 0 then begin
          LInImage   := True;
          LRgnRect.X := LXPos;
          LRgnRect.Y := LYPos;
          LRgnRect.Height := 1;
        end;
      end else begin
        if LAValue = 0 then begin
          LInImage       := False;
          LRgnRect.Width := LXPos - LRgnRect.X;
          GDPRegion.Union(LRgnRect);
        end;
      end;
      Inc(LpQuad);
    end;
    if LInImage then begin
      LInImage := False;
      LRgnRect.Width := LImgWidth - LRgnRect.X;
      GDPRegion.Union(LRgnRect);
    end;
    Inc(LScanLine, LDataStride);
  end;
  Result := True;

  GDPBitmap.UnlockBits(LBmpData);
end;

//-----------------------------------------------------------------------------
//  実際の描画処理を実行するDrawRoutineメソッド
//  引数のACanvasにこのメソッドの描画コードを実行する
//
//  描画開始位置は(DrawLeft, DrawTop)
//  幅と高さは DrawWidth, DrawHeight
//-----------------------------------------------------------------------------
procedure TplGPResizeLine.DrawRoutine(ACanvas: TCanvas; AGPBitmap: TGPBitmap;
  AOffsetX: Integer; AOffsetY: Integer);
var
  LGPGraphic    : TGPGraphics;
  LGPStartArrow : TGPAdjustableArrowCap;
  LGPEndArrow   : TGPAdjustableArrowCap;
  LPenWidth     : Single;
  LhBitmap      : HBITMAP;
  LBitmap       : TBitmap;
  LRetGPBitmap  : TGPBitmap;
  LStartX       : Integer;
  LStartY       : Integer;
  LEndX         : Integer;
  LEndY         : Integer;
begin
  inherited;

  //---------------------------------------------------------------------------
  //  自分自身以外への描画とAGPBitmapへの描画処理(画像として描画する場合)
  //  GetGPBitmapメソッドで取得したTGPBitmapを引数のAGPBitmapに描画する
  //  GetGPBitmapはこのDrawRoutineを実行して取得している
  //  そこでフラグを使用して2度実行しないようにしている
  //---------------------------------------------------------------------------
  if ACanvas <> nil then begin
    //描画コードはサイズを拡張したTGraphicControlに実行する
    if ACanvas = Self.Canvas then ACanvas := FExtendCtrl.Canvas;

    if (FCanDrawFlag) and (ACanvas <> FExtendCtrl.Canvas) then begin
      LBitmap      := TBitmap.Create;
      LRetGPBitmap := GetGPBitmap;
      try
        LBitmap.AlphaFormat := afDefined;
        LRetGPBitmap.GetHBITMAP(0, LhBitmap);
        LBitmap.Handle := LhBitmap;
        ACanvas.Draw(AOffsetX, AOffsetY, LBitmap);
      finally
        LBitmap.Free;
      end;
      exit;
    end;
  end else
  if (FCanDrawFlag) and (AGPBitmap <> nil) then begin
    LRetGPBitmap := GetGPBitmap;
    LGPGraphic   := TGPGraphics.Create(AGPBitmap);
    try
      LGPGraphic.DrawImage(LRetGPBitmap, AOffsetX, AOffsetY);
    finally
      FreeAndNil(LGPGraphic);
      FreeAndNil(LRetGPBitmap);
    end;
    exit;
  end;
  //---------------------------------------------------------------------------


  //実際の描画用コントロールの設定
  FExtendCtrl.SetBounds(Left - FExtendHorz, Top - FExtendVert,
                        Width + FExtendHorz * 2, Height + FExtendVert * 2);

  if Assigned(FGPPath) then FreeAndNil(FGPPath);
  if Assigned(FGPPen)  then FreeAndNil(FGPPen);

  FGPPen := TGPPen.Create(FPenColor);
  //拡大表示の設定
  LPenWidth := FPenWidth * ZoomValue / 100;
  if LPenWidth < 1.0 then LPenWidth := 1.0;
  FGPPen.SetWidth(LPenWidth);

  LGPStartArrow := TGPAdjustableArrowCap.Create(FArrowHeight, FArrowWidth, True);
  LGPEndArrow   := TGPAdjustableArrowCap.Create(FArrowHeight, FArrowWidth, True);
  if FStartArrow then begin
    LGPStartArrow.SetMiddleInset(FMiddleInset);
    FGPPen.SetCustomStartCap(LGPStartArrow);
  end;
  if FEndArrow then begin
    LGPEndArrow.SetMiddleInset(FMiddleInset);
    FGPPen.SetCustomEndCap(LGPEndArrow);
  end;


  if ACanvas = nil then begin
    LGPGraphic := TGPGraphics.Create(AGPBitmap);
  end else begin
    LGPGraphic := TGPGraphics.Create(ACanvas.Handle);
  end;


  FGPPath := TGPGraphicsPath.Create;
  try
    //-------------------------------------------------------------------------
    //  ここで描画の始点と終点を設定する
    //  AddLineメソッドの最初の引数が描画の始点,次が描画の終点
    //  X座標値にはFExtendHorzを,Y座標値にはFExtendVertを必ず加算しておく
    //-------------------------------------------------------------------------

    LStartX := DrawLeft;
    LStartY := DrawTop;
    LEndX   := DrawRight;
    LEndY   := DrawBottom;
    if FLineStartPos.X > FLineEndPos.X then begin
      LStartX := DrawRight;
      LEndX   := DrawLeft;
    end;
    if FLineStartPos.Y > FLineEndPos.Y then begin
      LStartY := DrawBottom;
      LEndY   := DrawTop;
    end;

    if FLineType = ltHorz then begin
      LStartY := (LStartY + LEndY) div 2;
      LEndY   := LStartY;
    end else
    if FLineType = ltVert then begin
      LStartX := (LStartX + LEndX) div 2;
      LEndX   := LStartX;
    end;

    FGPPath.AddLine(MakePoint(LStartX + FExtendHorz, LStartY + FExtendVert),
                    MakePoint(LEndX + FExtendHorz,   LEndY + FExtendVert));

    //-------------------------------------------------------------------------


    if AntiAliasing and (ZoomValue = 100) then begin
      LGPGraphic.SetSmoothingMode(SmoothingModeAntiAlias);
    end;
    LGPGraphic.DrawPath(FGPPen, FGPPath);
  finally
    FreeAndNil(LGPStartArrow);
    FreeAndNil(LGPEndArrow);
    FreeAndNil(LGPGraphic);
  end;
end;

{ TplExtendResizeLine }

//=============================================================================
//  コンストラクタ
//=============================================================================
constructor TplExtendResizeLine.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);

  ParentColor := False;
  Canvas.Brush.Style := bsClear;
  FLineCtrl := TplGPResizeLine(AOwner);
end;

//=============================================================================
 //  CM_HITTESTメッセージ処理
 //  マウスイベントは全て透過とする
//=============================================================================
procedure TplExtendResizeLine.CMHitTest(var Message: TCMHitTest);
begin
  Message.Result := HTNOWHERE;
end;

//=============================================================================
//  ペイントメソッド
//  ここで描画ルーチンを実行する
//=============================================================================
procedure TplExtendResizeLine.Paint;
begin
  inherited;
  FLineCtrl.DrawRoutine(Canvas, nil);
end;

end.