一、關於GDI+
從本質上來看,GDI+為開發人員提供了一組實現與各種裝置(例如監視器,印表機及其它具有圖形化能力但不及涉及這些圖形細節的裝置)進行互動的庫函數。GDI+的本質在於,它能夠替代開發人員實現與例如顯示器及其它外設的互動;而從開發人員角度來看,要實現與這些裝置的直接互動卻是一項艱巨的任務。
1展示了GDI+在開發人員與上述裝置之間起著重要的中介作用。其中,GDI+為我們“包辦”了幾乎一切—從把一個簡單的字串“HelloWorld”列印到控制台到繪製直線,矩形甚至是列印一個完整的表單等。
圖1.GDI+擔當著重要的中介作用
那麼,GDI+是如何工作的呢?為了弄清這個問題,讓我們來分析一個樣本—繪製一條線段。實質上,一條線段就是一個從一個開始位置(X0,Y0)到一個結束位置(Xn,Yn)的一系列像素點的集合。為了畫出這樣的一條線段,裝置(在本例中指顯示器)需要知道相應的裝置座標或物理座標。
然而,開發人員不是直接告訴該裝置,而是調用GDI+的drawLine()方法,然後,由GDI+在記憶體(即“視頻記憶體”)中繪製一條從點A到點B的直線。GDI+讀取點A和點B的位置,然後把它們轉換成一個像素序列,並且指令監視器顯示該像素序列。簡言之,GDI+把裝置獨立的調用轉換成了一個裝置可理解的形式;或者實現相反方向的轉換。
至此,我們已經簡單瞭解了GDI+的工作機理。現在,讓我們開始探討如何?一些基本的映像操作。
二、映像操作—縮圖,縮放與儲存
在本文樣本中,我們將實現如下的任務:
1. 建立縮圖。
2. 縮放一個載入的映像。
3. 儲存一個操作中的映像。
a) 建立縮圖
縮圖是映像的濃縮版本。典型情況下,一幅縮圖映像的尺寸為80×200像素。在GDI+中,一個映像的縮圖可以通過使用Image類的GetThumbnailImage()方法來建立。其函數原型如下:
public Image GetThumbnailImage (
int thumbWidth,
int thumbHeight,
GetThumbnailImageAbort callback,
IntPtr callbackData
) |
第一個參數相應於縮圖的寬度;第二個參數相應於產生的縮圖的高度;第三個參數是一個Image.GetThumbnailImageAbort委託。在 GDI+ 1.0 版中不使用此委託。即便如此,也必須建立一個委託並在該參數中傳遞對此委託的引用。第四個參數同樣沒有使用,但是也需要提供以實現相容性。注意,第四個參數必須為IntPtr.Zero。
如果前兩個參數(也就是寬度和高度)都為0的話,那麼,GDI+返回一個嵌入式縮圖。否則,使用系統定義尺寸建立該縮圖。例如,如果img是一個映像類的執行個體,並且使用的寬度和高度都是系統定義的,建立一個縮圖的語句應該如下所示:
Image thumbNailImage = img.GetThumbnailImage(0,0,tnCallBack,IntPtr.Zero); |
在此,thumbNailImage包含返回的縮圖,而tnCallback是一個相應於Image.GetThumbnailImageAbort的函數,其定義如下:
//必須調用它,但是沒有使用
style='font-size:10.0pt;font-family:Verdana'>publicbool tnCallbackMethod()
...{
return false;
} |
b) 縮放一個載入的映像
縮放是放大或縮小一個映像的過程—通過在映像尺寸上乘以縮放因子實現。其中,縮放因子=期望的映像尺寸/當前映像尺寸。例如,要把一個映像放大200%,則當前尺寸必須乘以200%(200%=200/100=2);為了縮小一個映像到25%,則當前尺寸必須乘以25%或0.25(25/100=0.25倍)。
c) 儲存映像
儲存操作是映像操作中的關鍵操作之一。在儲存一個映像時,映像相應的類型資訊也必須進行儲存;也就是說,該映像的副檔名在這一過程中具有重要角色。每一種類型相應於一個特定的格式。 實質上,在儲存一個映像時,根據該格式輸出資料是非常必要的。然而,藉助於GDI+ API的優勢,一個對Image類的Save()方法的簡單調用就可以把相應的寫資料操作中所有細節省略掉。這個方法使用兩個參數—被儲存的映像的名字和待儲存映像的格式。該格式能夠通過ImageFormat類提供的類型來指定。下列表格指定了GDI+支援的各種映像格式。
屬性 描述
Bmp 指定BMP格式。
Emf 指定EMF(增強元檔案格式)。
Exif 指定EXIF格式。
Gif 指定GIF格式。
Guid 指定一個GUID結構,用於描述ImageFormatobject。
Icon 指定Windows表徵圖格式。
Jpeg 指定JPEG格式。
MemoryBmp 指定記憶體位元影像格式。
Png 指定PNG格式。
Tiff 指定TIFF格式。
Wmf 指定WMF(Windows元檔案格式)。
其中,Emf和Wmf是特定於Windows系統的。
假定你想使用名字“checker.gif”儲存一個映像,那麼,相應的實現語句將是:
curImage.Save(“checker.gif”,ImageFormat.Gif); |
這裡,curImage對應於Image類的執行個體。
在下一節中,我將對前面開發的這個應用程式進行擴充。
三、實際開發中的映像操作
下面,我們來討論實際中的使用方式。我將在本文應用程式範例中添加下列功能:
1. 以使用者指定的格式儲存映像。
2. 根據從菜單下選擇的百分比放大映像。
3. 建立一個載入映像的略縮圖。
相應的菜單操作如下所示:
mnuSave—檔案菜單下儲存映像的子功能表。
mnu200Zoom—放大映像200%。
mnuThumbNail—建立映像的一個略縮圖。
下面是處理功能表項目mnuSave的Click事件相應的方法:
private void mnuSave_Click(object sender,System.EventArgs e)
...{
//如果映像已經建立
if(curImage == null)
return;
//調用SaveFileDialog對話方塊
SaveFileDialog saveDlg = new SaveFileDialog();
saveDlg.Title = "Save Image As";
saveDlg.OverwritePrompt = true;
saveDlg.CheckPathExists = true;
saveDlg.Filter =
"Bitmap File(*.bmp)|*.bmp|" +
"Gif File(*.gif)|*.gif|" +
"JPEG File(*.jpg)|*.jpg|" +
"PNG File(*.png)|*.png" ;
saveDlg.ShowHelp = true;
//如果選擇,則進行儲存
if(saveDlg.ShowDialog() == DialogResult.OK)
...{
//得到使用者選擇的檔案名稱
string fileName = saveDlg.FileName;
//得到副檔名
string strFilExtn =fileName.Remove(0,fileName.Length - 3);
//儲存檔案
switch(strFilExtn)
...{
case "bmp":
curImage.Save(fileName, ImageFormat.Bmp);
break;
case "jpg":
curImage.Save(fileName, ImageFormat.Jpeg);
break;
case "gif":
curImage.Save(fileName, ImageFormat.Gif);
break;
case "tif":
curImage.Save(fileName, ImageFormat.Tiff);
break;
case "png":
curImage.Save(fileName, ImageFormat.Png);
break;
default:
break;
}
}
} |
首先,以可接收的副檔名顯示這個儲存對話方塊。然後,由從該對話方塊返回的檔案名稱檢索相應的副檔名。最後,根據該副檔名,使用相應的映像格式參數調用Save()方法。
接下來,我們分析功能表項目mnu200Zoom相應的處理器。首先,讓我們在應用程式級添加下列以粗體顯示的一行:
private double curZoom=1.0;
private Image curImage=null;//用於儲存當前映像
private int i = 0;//用於把螢幕重畫操作與縮圖繪製部分區別開來
然後,必須對mnuLoad處理代碼作少許調整,如下所示:
private void mnuLoad_Click(object sender,System.EventArgs e)
...{
//建立OpenFileDialog
OpenFileDialog opnDlg = new OpenFileDialog();
//設定一個映像類型過濾器
opnDlg.Filter =
"All Image files|*.bmp;*.gif;*.jpg;*.ico;"+
"*.emf;,*.wmf|Bitmap Files(*.bmp;*.gif;*.jpg;"+
"*.ico)|*.bmp;*.gif;*.jpg;*.ico|"+
"Meta Files(*.emf;*.wmf;*.png)|*.emf;*.wmf;*.png";
opnDlg.Title = "開啟影像檔";
opnDlg.ShowHelp = true;
//如果OK,選擇它
if(opnDlg.ShowDialog() == DialogResult.OK)
...{
//讀取當前選擇的檔案名稱
curFileName = opnDlg.FileName;
//使用Image.FromFile建立映像對象
try
...{
curImage = Image.FromFile(curFileName);
}
catch(Exception exp)
...{
MessageBox.Show(exp.Message);
}
}
//改變AutoScrollMinSize屬性
this.AutoScrollMinSize = new Size
((int)(curImage.Width * curZoom),
(int)(curImage.Height * curZoom));
i++;
//重新繪製表單
Invalidate();
} |
注意,在此新添加的代碼分別在原來的映像寬度和高度上乘以放大因子以產生一個放大的映像。然後,必須相應地修改paint事件的處理器。如下所示:
private void Form1_Paint(object sender, PaintEventArgs e)
...{
if (curImage != null && i==0)
...{
Graphics g = this.CreateGraphics();
g.DrawImage(curImage, new Rectangle
(AutoScrollPosition.X,
AutoScrollPosition.Y ,
(int)(this.ClientRectangle.Width * curZoom),
(int)(ClientRectangle.Height * curZoom)));
}
} |
該映像應該有根據放大因子的相應的高度和寬度。下面,我們來看一下mnu200Zoom功能表項目相應的事件處理器:
private void mnu200_Click(object sender,System.EventArgs e)
...{
if(curImage != null)
...{
curZoom = (double)200/100;
i++;
Invalidate();
}
} |
最後,我們來看一下mnuThumbNail功能表項目相應的事件處理器:
1private void mnuThumbnail_Click(object sender, EventArgs e)
2...{
3if(curImage != null)
4...{
5i++;
6//回調
7Image.GetThumbnailImageAbort tnCallBack =
8new Image.GetThumbnailImageAbort(tnCallbackMethod);
9//得到縮圖映像
10Image thumbNailImage = curImage.GetThumbnailImage
11(100, 100, tnCallBack, IntPtr.Zero);
12//建立一個Graphics對象
13Graphics tmpg = this.CreateGraphics();
14tmpg.Clear(this.BackColor);
15//畫縮圖映像
16tmpg.DrawImage(thumbNailImage, 10, 10, thumbNailImage.Width, thumbNailImage.Height);
17//釋放掉Graphics對象
18tmpg.Dispose();
19}
20
21}
22 |
在此,我們首先建立一個GetThumbnailImageAbort類型的變數並且賦給它值tnCallbackMethod()—這是通過傳遞給該方法GetThumbnailImageAbort實現的。然後,它建立一個新的Image類的執行個體以儲存GetThumbnailImage方法返回的映像—此後,這個方法將用於把縮圖繪製到螢幕上。
四、小結
在本文中,我僅討論了.NET C#環境下關於GDI+編程的一些基本的實用操作片斷。在以後的文章中,我們將逐漸展開對.NET GDI+編程進階特徵的探討。
【注】 ①本文源碼在Windows XP Professonal+VS2005環境下調試通過;
②本樣本中私人變數的i的引入僅為了把螢幕重繪與縮圖繪製區別開來,讀者可考慮其它更巧妙的辦法;
③映像重繪及滾動過程中出現螢幕抖動現象,讀者可結合有關映像繪製雙緩衝技術予以改進。