關於GDI+的幾何變換類Matrix的理論和應用有很多書籍和文章介紹,本文只是談一點自己的應用心得。
使用GDI+的Matrix類,可以很方便的進行提供了Rotate(旋轉)、Scale( 縮放)、Shear(切變)等線性變換和Translate(平移),還可以通過這幾個基本的變換組成更複雜的複合變換。通過變換後的圖形及原點座標變化很大,如下面的語句:
g.DrawEllipse(Pens.Blue, 0, 0, 100, 50);
g.ScaleTransform(1.0, 0.5);
g.TranslateTransform(50.0, 0.0, moAppend);
g.RotateTransform(30, moAppend);
g.DrawEllipse(Pens.Blue, 0, 0, 100, 50);
將產生下面的映像:
但是,我們有時需要知道變換後的圖形尺寸及座標,如果通過編程計算求得,可能很麻煩,特別是一些複雜的組合變換。利用Matrix提供的變換後的矩陣資料,可以很簡單的得到變換後的圖形矩形。
Matrix提供的是一個3 X 2 仿射矩陣,上面的2 X 2 矩陣是Rotate、Scale和Shear等線性變換後的資料,分別用m11,m21,m12,m22表示,可以用x = m11 * xi+ m21 * yi,y = m21 * xi + m22 * yi求得線性變換後圖形的任意一點的相對座標,我們只要計算四個角的座標就行了;下面一行則是平移資料,用dx,dy表示,前面計算出的座標點分別加上dx和dy就是實際的座標點。下面是筆者寫的一個計算函數和測試代碼:
function CalcTransRect(Origin: TGpRectF; e: TMatrixElements): TGpRectF;
function GetTransPoint(x, y: Single): TGpPointF;
begin
Result.X := e.m11 * x + e.m21 * y;
Result.Y := e.m12 * x + e.m22 * y;
end;
var
R: TGpRectF;
pf: array[0..3] of TGpPointF;
I: Integer;
begin
R := Origin;
// 分別計算四個角的相對座標
pf[0] := GetTransPoint(R.X, R.Y);
pf[1] := GetTransPoint(R.Width + R.X, R.Y);
pf[2] := GetTransPoint(R.X, R.Height + R.Y);
pf[3] := GetTransPoint(R.Width + R.X, R.Height + R.Y);
// 取得左上方和右下角的座標點
R := GpRect(pf[0].X, pf[0].Y, 0.0, 0.0);
for I := 0 to 3 do
begin
if R.X > pf[I].X then R.X := pf[I].X
else if R.Width < pf[I].X then R.Width := pf[I].X;
if R.Y > pf[I].Y then R.Y := pf[I].Y
else if R.Height < pf[I].Y then R.Height := pf[I].Y;
end;
// 求出矩形尺寸
R.Width := R.Width - R.X;
R.Height := R.Height - R.Y;
// 求得左上方實際座標
R.X := R.X + e.dx;
R.Y := R.Y + e.dy;
Result := R;
end;
procedure TMainForm.Button1Click(Sender: TObject);
var
Image: TGpImage;
g: TGpGraphics;
m: TGpMatrix;
r: TGpRectF;
begin
Image := TGpImage.Create('....Mediamsn.jpg');
g := TGpGraphics.Create(Handle, False);
m := TGpMatrix.Create;
try
r := GpRect(20.0, 20.0, Image.Width, Image.Height);
g.Clear(ARGBFromTColor(Color));
g.DrawImage(Image, r);
m.Rotate(30);
m.Scale(1.8, 0.8);
m.Shear(0.3, 0.2);
g.SetTransform(m);
g.DrawImage(Image, r);
r := CalcTransRect(r, m.Elements);
g.ResetTransform;
g.DrawRectangle(Pens.Red, r);
finally
Image.Free;
g.Free;
m.Free;
end;
end;
運行結果如,紅色的矩形是計算出的變換後的座標,與變換後的映像完全吻合:
還有的時候,我們需要精確控製圖形的變換,比如,以圖形左上方為原點縮放、以圖形中心點進行旋轉等。由於GDI+的幾何變換是以Graphics畫布原點,而不是以圖形左上方為原點進行的,所以必須使用平移進行座標變換,然後採用相對座標畫圖。比如,我們希望上面的例子以畫布20,20為原點(即無論怎樣變換,左上方座標不變)進行線性變換:
g.DrawImage(Image, 20, 20, Image.Width, Image.Height); //用絕對座標畫原圖
m.Translate(20, 20); // 座標轉換
m.Rotate(30);
m.Scale(1.8, 0.8);
m.Shear(0.3, 0.2);
g.SetTransform(m);
g.DrawImage(Image, 0, 0, Image.Width, Image.Height); //用相對座標畫圖
其結果為,完全符合要求:
這個問題似乎很簡單,但是我們忽略了一個變換順序問題,前面所有的變換操作都是以預設順序moPrepend進行的,如果改為moAppend是否符合我們的要求呢?答案是否!因為moPrepend是在上一個變換操作完成前進行的,也就是說無論是圖形形狀還是座標位置,都是和上一變換操作同步進行的;而moAppend則是在上一變換操作完成後進行的,這時,圖形形狀和座標點都已經變了,在已經改變了形狀和座標位置的基礎上繼續當前變換,肯定的不到我們預想的效果。下面的圖是改變上面三個變換操作的順序為moAppend後的運行結果:
為此,我們不能再用座標變換後的相對座標進行操作,而是需要在每個變換操作前預置座標位置和變換操作後還原座標位置,以便當前和後續變換都是正確的。Matrix已經提供了一個RotateAt,我們只要再寫ScaleAt和ShearAt就行了,這樣一來,無論操作順序如何,變換操作位置都在我們的預期範圍內。下面是這2個過程和測試代碼:
procedure ScaleAt(m: TGpMatrix; ScaleX, ScaleY, x, y: Single; Order: TMatrixOrder = moPrepend);
begin
if order = moAppend then
begin
x := -x;
y := -y;
end;
m.Translate(x, y, Order);
m.Scale(ScaleX, ScaleY, Order);
m.Translate(-x, -y, Order);
end;
procedure ShearAt(m: TGpMatrix; ShearX, ShearY, x, y: Single; Order: TMatrixOrder = moPrepend);
begin
if order = moAppend then
begin
x := -x;
y := -y;
end;
m.Translate(x, y, Order);
m.Shear(ShearX, ShearY, Order);
m.Translate(-x, -y, Order);
end;
procedure TMainForm.Button2Click(Sender: TObject);
var
Image: TGpImage;
g: TGpGraphics;
m: TGpMatrix;
begin
Image := TGpImage.Create('....Mediamsn.jpg');
g := TGpGraphics.Create(Handle, False);
m := TGpMatrix.Create;
try
g.DrawImage(Image, 20, 20, Image.Width, Image.Height);
m.RotateAt(30, GpPoint(20.0, 20.0), moAppend);
ScaleAt(m, 1.8, 0.8, 20, 20, moAppend);
ShearAt(m, 0.3, 0.2, 20, 20, moAppend);
g.SetTransform(m);
g.DrawImage(Image, 20, 20, Image.Width, Image.Height);
finally
Image.Free;
g.Free;
m.Free;
end;
end;
運行結果如,如果改為預設操作順序,則和前面平移座標後的運行結果完全一樣:
說明,本文所用GDI+代碼與網上流通的不相容,如上面例子中的TMatrixElements,定義為:
TMatrixElements = packed record
case Integer of
0: (Elements: array[0..5] of Single);
1: (m11, m12, m21, m22, dx, dy: Single);
end;
而網上流通的則定義為一個數組:
TMatrixArray = array[0..5] of Single;
如有錯誤,請來信指正:maozefa@hotmail.com