Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2012年12月09日] [更新 2012年12月09日] GDI+ 関係サンプル
GDI+ 関係サンプル 
G070_ワールド座標系とページ座標系
動作確認等 Windows 7 U64(SP1) + Delphi XE(UP1) Pro
G070_GDIPlus_TranslateTransformScale.zip [1,196 KB] 2012年12月09日版 (EXE 同梱)







ワールド座標系とページ座標系


座標の原点,(0, 0) は,通常,コントロールや Canvas の左上の隅です.

GDI+ では,この原点を移動して設定することができます.また,X軸,Y軸の倍率の設定,つまりスケーリングも設定することができます.
この機能は,例えば,関数のグラフの作成に使えます.原点の移動をしなければ,単なる拡大・縮小表示にも利用可能です.このページのサンプルは,単にこの機能を確認するだけのプログラムです.

原点がデフォルトの左上の時,GDI+ では「ワールド座標系」,原点を再設定したり,軸のスケーリングをした座標を「ページ座標系」と言っています.「デバイス座標系」というのもありますが,ここでは省略します.





01_座標の原点移動と軸のスケーリング


このサンプルでは以下のコントロールを使用しています.
  • TrackBar1 X 軸方向の原点移動量の設定
  • TrackBar2 Y 軸方向の原点移動量の設定
  • ComboBox1 X 軸と Y 軸のスケーリング (X, Y 軸共同じ値)
原点の移動とスケーリングには以下の関数を使用します.
  • 原点の設定関数 TGPGraphics.TranslateTransform
  • スケーリングの関数 TGPGraphics.ScaleTransform
画像と文字列の描画は,次のようになっています.描画の始点は (0, 0) となっています.

  GDPGraphic.DrawImage(GDPImage, MakeRect(0, 0, 100, 100));
  GDPGraphic.DrawString('DELPHI', -1, GDPFont, MakePoint(0.0, 0.0), GDPBrush);

プログラムを実行して,トラックバーを操作すると,画像と文字列が画面上を移動します.通常であれば,描画始点が (0, 0) となっていますから,左上に表示されるはずのものが移動して表示されます.これは,原点が移動し,新しいワールド座標系の原点を始点として描画されるからです.

このサンプルでは,原点を移動した後にスケーリングを実行しています.これを逆にすると,スケーリング後の値で原点移動します.したがって結果は違ってくることになります.


図1
設計時画面
図2
実行開始時の画面
  • 描画始点が (0, 0) なので左上から表示されている
図3
トラックバーを操作
  • 座標の原点が変わったため,画像と文字列が移動する

次に,コンボボックスでスケーリングの値を変更してみます.
画像と文字列が拡大するのが確認できます.軸スケールそのものが拡大されます.したがって,描画内容である線の太さ等も拡大または縮小されることになります.


図4
スケーリングを変更した結果
  • 描画用コントロールである TMyPanel の描画スケールが変わる

リスト1
ワールド座標系とページ座標系の確認のためのテスト
unit Unit1;

interface

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

type
  TMyPanel = class(TPanel)
  private
    FXLocation : Single;
    FYLocation : Single;
    FScale     : Single;
    procedure SetXLocation(const Value: Single);
    procedure SetYLocation(const Value: Single);
    procedure SetScale(const Value: Single);
  protected
    procedure Paint; override;

  public
    constructor Create(AOwner: TComponent); override;
    property XLocation : Single read FXLocation write SetXLocation;
    property YLocation : Single read FYLocation write SetYLocation;
    property Scale     : Single read FScale     write SetScale;
  end;

  TForm1 = class(TForm)
    TrackBar1: TTrackBar;
    TrackBar2: TTrackBar;
    ComboBox1: TComboBox;
    procedure FormCreate(Sender: TObject);
    procedure TrackBar1Change(Sender: TObject);
    procedure TrackBar2Change(Sender: TObject);
    procedure ComboBox1Change(Sender: TObject);
  private
    { Private 宣言 }
    MyPanel : TMyPanel;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

//=============================================================================
//  フォーム生成時の処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  Caption := 'GDI+  ページ座標と軸方向の倍率';

  //描画するコントロールを生成
  MyPanel := TMyPanel.Create(Self);
  with MyPanel do begin
    Parent := Self;
    SetBounds(5, 70, Self.ClientWidth - 10, Self.ClientHeight - 75);
    DoubleBuffered   := True;
    ControlStyle     := MyPanel.ControlStyle + [csOpaque];
    BevelOuter       := bvNone;
    Visible          := True;
    Color            := clWhite;
    ParentBackground := False;
    Anchors          := [akLeft, akTop, akRight, akBottom];
  end;

  //水平方向への原点行移動
  with TrackBar1 do begin
    ShowSelRange := False;
    TickStyle    := tsNone;
    Height   := 26;
    Min      := -250;
    Max      := 250;
    Position := 0;
  end;

  //垂直方向への原点移動
  with TrackBar2 do begin
    ShowSelRange := False;
    TickStyle    := tsNone;
    Height   := 26;
    Min      := -150;
    Max      := 150;
    Position := 0;
  end;

  //スケーリング
  ComboBox1.Items.Clear;
  ComboBox1.Style := csDropDownList;
  ComboBox1.Items.CommaText := '0.2, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0';
  ComboBox1.ItemIndex := 2;
end;

//=============================================================================
//  TrackBar1のOnClickイベント処理
//  X軸方向の座標原点の移動
//=============================================================================
procedure TForm1.TrackBar1Change(Sender: TObject);
begin
  if not Assigned(MyPanel) then exit;
  MyPanel.XLocation := TrackBar1.Position;
end;

//=============================================================================
//  TrackBar2のOnClickイベント処理
//  Y軸方向の座標原点の移動
//=============================================================================
procedure TForm1.TrackBar2Change(Sender: TObject);
begin
  if not Assigned(MyPanel) then exit;
  MyPanel.YLocation :=  TrackBar2.Position;
end;

//=============================================================================
//  ComboBox1のOnClickイベント処理
//  座標のスケーリング設定
//=============================================================================
procedure TForm1.ComboBox1Change(Sender: TObject);
begin
  if not Assigned(MyPanel) then exit;
  MyPanel.Scale := StrToFloat(ComboBox1.Items[ComboBox1.ItemIndex]);
end;

{ TMyPanel }

//-----------------------------------------------------------------------------
//  コンストラクタ
//  各種プロパティの初期化
//-----------------------------------------------------------------------------
constructor TMyPanel.Create(AOwner: TComponent);
begin
  inherited;

  FXLocation := 0.0;
  FYLocation := 0.0;
  FScale     := 1.0;
end;

//-----------------------------------------------------------------------------
//  XLocationプロパティ設定用メソッド
//  X軸方向の原点移動
//-----------------------------------------------------------------------------
procedure TMyPanel.SetXLocation(const Value: Single);
begin
  if not Assigned(Parent) then exit;

  if FXLocation <> Value then begin
     FXLocation := Value;
     Invalidate;
  end;
end;

//-----------------------------------------------------------------------------
//  YLocationプロパティ設定用メソッド
//  Y軸方向の原点移動
//-----------------------------------------------------------------------------
procedure TMyPanel.SetYLocation(const Value: Single);
begin
  if not Assigned(Parent) then exit;

  if FYLocation <> Value then begin
     FYLocation := Value;
     Invalidate;
  end;
end;

//-----------------------------------------------------------------------------
//  Scaleプロパティ設定用メソッド
//  座標軸のスケーリング
//-----------------------------------------------------------------------------
procedure TMyPanel.SetScale(const Value: Single);
begin
  if not Assigned(Parent) then exit;

  if FScale <> Value then begin
     FScale := Value;
     Invalidate;
  end;
end;

//-----------------------------------------------------------------------------
//  TMyPanelクラスのPaintメソッド
//  実際の描画コード
//-----------------------------------------------------------------------------
procedure TMyPanel.Paint;
var
  LImageFilePath : String;
  LGPImage       : TGPImage;
  LGPGraphic     : TGPGraphics;
  LGPFont        : TGPFont;
  LGPBrush       : TGPSolidBrush;
begin
  inherited;

  //画像ファイルを指定.TGPImaageインスタンスを生成して読み込む
  LImageFilePath := ExtractFilePath(ParamStr(0)) + '503.bmp';
  LGPImage := TGPImage.Create(LImageFilePath);

  //このTMyPanelのCanvasを描画先にしてTGPGraphicsのインスタンスを生成
  LGPGraphic := TGPGraphics.Create(Canvas.Handle);

  //念のためリセット
  LGPGraphic.ResetTransform;

  //座標の原点を (FXLocation, FYLocation) に移動する
  //これをページ座標とも言う
  LGPGraphic.TranslateTransform(FXLocation, FYLocation);
  //水平方向(X軸方向),垂直方向(Y軸方向)の倍率を共にFScale倍にする
  LGPGraphic.ScaleTransform(FScale, FScale);

  //上の設定で,画像オブジェクトを描画
  //描画始点は (0, 0) となっているが,ワールド座標では (FXLocation, FYLocation)
  LGPGraphic.DrawImage(LGPImage, MakeRect(0, 0, 100, 100));

  //フォントとブラシを生成して文字列を描画
  //これも描画始点は (0, 0)
  LGPFont  := TGPFont.Create('Arial', 30, FontStyleRegular, UnitPixel);
  LGPBrush := TGPSolidBrush.Create(MakeColor(255, 0, 0, 0));
  LGPGraphic.DrawString('DELPHI', -1, LGPFont, MakePoint(0.0, 0.0), LGPBrush);

  //Createしたオブジェクトは解放する
  LGPFont.Free;
  LGPBrush.Free;
  LGPImage.Free;
  LGPGraphic.Free;
end;

end.




02_ページ座標値とワールド座標値の変換


次は,座標の原点が移動した座標系,マイクロソフトの解説で言うところのページ座標系での値を,元々の,左上が原点 (0, 0) であるワールド座標系の座標値に変換するテストです.

移動後の原点と,スケーリングの値から計算してもかまいませんが,TransformPoints という関数が利用できます.この関数は引数の指定により,逆の変換もできます.サンプルでは,この関数を使用して,画像の表示領域の矩形を描画します.
当然ですが,画像を囲む枠線を描画するだけであれば,わさわざ変換しなくても,ページ座標系の座標値でできます.


図5
実行開始画面
図6
実行結果例
  • 画像を囲む枠線が描画されている
  • コンボボックスで描画スケールを変更しているので,TMyPanel の描画スケールが変わっている

リスト2
ページ座標系の座標値を,ワールド座標系の座標値に変換
unit Unit1;

interface

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

type
  TMyPanel = class(TPanel)
  private
    FXLocation : Single;
    FYLocation : Single;
    FScale     : Single;
    procedure SetXLocation(const Value: Single);
    procedure SetYLocation(const Value: Single);
    procedure SetScale(const Value: Single);
  protected
    procedure Paint; override;

  public
    constructor Create(AOwner: TComponent); override;
    property XLocation : Single read FXLocation write SetXLocation;
    property YLocation : Single read FYLocation write SetYLocation;
    property Scale     : Single read FScale     write SetScale;
  end;

  TForm1 = class(TForm)
    TrackBar1: TTrackBar;
    TrackBar2: TTrackBar;
    ComboBox1: TComboBox;
    procedure FormCreate(Sender: TObject);
    procedure TrackBar1Change(Sender: TObject);
    procedure TrackBar2Change(Sender: TObject);
    procedure ComboBox1Change(Sender: TObject);
  private
    { Private 宣言 }
    MyPanel : TMyPanel;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

//=============================================================================
//  フォーム生成時の処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  Caption := 'GDI+  ワールド座標系の座標値に変換';

  //描画するコントロールを生成
  MyPanel := TMyPanel.Create(Self);
  with MyPanel do begin
    Parent := Self;
    SetBounds(5, 70, Self.ClientWidth - 10, Self.ClientHeight - 75);
    DoubleBuffered   := True;
    ControlStyle     := MyPanel.ControlStyle + [csOpaque];
    BevelOuter       := bvNone;
    Visible          := True;
    Color            := clWhite;
    ParentBackground := False;
    Anchors          := [akLeft, akTop, akRight, akBottom];
  end;

  //水平方向への原点行移動
  with TrackBar1 do begin
    ShowSelRange := False;
    TickStyle    := tsNone;
    Height   := 26;
    Min      := -250;
    Max      := 250;
    Position := 0;
  end;

  //垂直方向への原点移動
  with TrackBar2 do begin
    ShowSelRange := False;
    TickStyle    := tsNone;
    Height   := 26;
    Min      := -150;
    Max      := 150;
    Position := 0;
  end;

  //スケーリング
  ComboBox1.Items.Clear;
  ComboBox1.Style := csDropDownList;
  ComboBox1.Items.CommaText := '0.2, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0';
  ComboBox1.ItemIndex := 2;
end;

//=============================================================================
//  TrackBar1のOnClickイベント処理
//  X軸方向の座標原点の移動
//=============================================================================
procedure TForm1.TrackBar1Change(Sender: TObject);
begin
  if not Assigned(MyPanel) then exit;
  MyPanel.XLocation := TrackBar1.Position;
end;

//=============================================================================
//  TrackBar2のOnClickイベント処理
//  Y軸方向の座標原点の移動
//=============================================================================
procedure TForm1.TrackBar2Change(Sender: TObject);
begin
  if not Assigned(MyPanel) then exit;
  MyPanel.YLocation :=  TrackBar2.Position;
end;

//=============================================================================
//  ComboBox1のOnClickイベント処理
//  座標のスケーリング設定
//=============================================================================
procedure TForm1.ComboBox1Change(Sender: TObject);
begin
  if not Assigned(MyPanel) then exit;
  MyPanel.Scale := StrToFloat(ComboBox1.Items[ComboBox1.ItemIndex]);
end;

{ TMyPanel }

//-----------------------------------------------------------------------------
//  コンストラクタ
//  各種プロパティの初期化
//-----------------------------------------------------------------------------
constructor TMyPanel.Create(AOwner: TComponent);
begin
  inherited;

  FXLocation := 0.0;
  FYLocation := 0.0;
  FScale     := 1.0;
end;

//-----------------------------------------------------------------------------
//  XLocationプロパティ設定用メソッド
//  X軸方向の原点移動
//-----------------------------------------------------------------------------
procedure TMyPanel.SetXLocation(const Value: Single);
begin
  if not Assigned(Parent) then exit;

  if FXLocation <> Value then begin
     FXLocation := Value;
     Invalidate;
  end;
end;

//-----------------------------------------------------------------------------
//  YLocationプロパティ設定用メソッド
//  Y軸方向の原点移動
//-----------------------------------------------------------------------------
procedure TMyPanel.SetYLocation(const Value: Single);
begin
  if not Assigned(Parent) then exit;

  if FYLocation <> Value then begin
     FYLocation := Value;
     Invalidate;
  end;
end;

//-----------------------------------------------------------------------------
//  Scaleプロパティ設定用メソッド
//  座標軸のスケーリング
//-----------------------------------------------------------------------------
procedure TMyPanel.SetScale(const Value: Single);
begin
  if not Assigned(Parent) then exit;

  if FScale <> Value then begin
     FScale := Value;
     Invalidate;
  end;
end;

//-----------------------------------------------------------------------------
//  TMyPanelクラスのPaintメソッド
//  実際の描画コード
//-----------------------------------------------------------------------------
procedure TMyPanel.Paint;
var
  LPoints        : array [0..1] of TGPPointF;
  LObjWidth      : Single;
  LObjHeight     : Single;
  LStartX        : Single;
  LStartY        : Single;
  LImageFilePath : String;
  LGPImage       : TGPImage;
  LGPGraphic     : TGPGraphics;
  LGPPen         : TGPPen;
  LObjRect       : TGPRectF;
begin
  inherited;

  //元の画層のサイズではなく,別の幅と高さで描画する
  LObjWidth  := 100.0;
  LObjHeight := 100.0;
  //描画始点
  LStartX := 0.0;
  LStartY := 0.0;

  //画像ファイルを指定.TGPImaageインスタンスを生成して読み込む
  LImageFilePath := ExtractFilePath(ParamStr(0)) + '503.bmp';
  LGPImage := TGPImage.Create(LImageFilePath);

  //このTMyPanelのCanvasを描画先にしてTGPGraphicsのインスタンスを生成
  LGPGraphic := TGPGraphics.Create(Canvas.Handle);

  //念のためリセット
  LGPGraphic.ResetTransform;

  //座標の原点を (FXLocation, FYLocation) に移動する
  //これをページ座標と言う
  LGPGraphic.TranslateTransform(FXLocation, FYLocation);
  //水平方向(X軸方向),垂直方向(Y軸方向)の倍率を共にFScale倍にする
  LGPGraphic.ScaleTransform(FScale, FScale);

  //上の設定で,画像オブジェクトを描画
  LGPGraphic.DrawImage(LGPImage, MakeRect(LStartX, LStartY, LObjWidth, LObjHeight));


  //---------------------------------------------------------------------------
  //  以下,ページ座標系の座標値をワールド座標系の値に変換するコード
  //  画像オブジェクトを囲む矩形を描画する
  //  ページ座標系のままで描画してもよいが,軸のスケールを変換していると,線の
  //  太さも拡大・縮小されてしまう
  //---------------------------------------------------------------------------

  //描画開始座標値と対角点の座標の値を配列に格納
  LPoints[0] := MakePoint(0.0, 0.0);
  LPoints[1] := MakePoint(LObjWidth, LObjHeight);

  //ワールド座標系の値に変換
  LGPGraphic.TransformPoints(CoordinateSpacePage,
                             CoordinateSpaceWorld,
                             PGPPointF(@LPoints),
                             Length(LPoints));

  //座標系をワールド座標系に戻す
  LGPGraphic.ResetTransform;
  //ペンのオブジェクト生成し,それで四角形を描く
  LGPPen := TGPPen.Create(aclBlack);
  LGPPen.SetDashStyle(DashStyleDash);
  LObjRect := MakeRect(LPoints[0].X,
                       LPoints[0].Y,
                       LPoints[1].X - LPoints[0].X,
                       LPoints[1].Y - LPoints[0].Y);
   LGPGraphic.DrawRectangle(LGPPen, LObjRect);

  //Createしたオブジェクトは解放する
  LGPPen.Free;
  LGPImage.Free;
  LGPGraphic.Free;
end;

end.