Subscribe

RSS Feed (xml)

Powered By

Skin Design:
Free Blogger Skins

Powered by Blogger

星期一, 10月 22, 2007

視窗繪圖基本概念

處理畫面經常使用到Canvas物件,此物件定義於Graphics單元裡,其屬性有
Brush,其同時屬於TCanvas及TForm物件裡,在TShape物件中,Brush是用來決定填充的背景顏色或樣式,而當Brush用於Controls時,只能用於繪製背景,且此時Brush是唯讀且Run-time的屬性。

  GDI是Graphics Device Interface 的縮寫,是作業系統在螢幕以及印表機上繪製圖形的次性統,同時也提供給應用程式透過GDI函式庫與資料結構,處理顯示與列印等相關工作,不同於之前練習的簡單空白視窗,讓我們將利用GDI處理一些有趣的東西 !!

  基本上,視窗作業系統是採取將硬體與應用程式隔離開的策略,應用程式被禁止直接存取硬體的資源,所有跟硬體相關的工作都是由作業系統代為完成,也包括繪圖與文字顯示(文字是由許多點繪製而成)的部分,往往理所當然的簡單工作,在手工打造的過程下卻往往呈現繁複而細緻的樣貌,而GDI正是視窗應用程式用於圖形處理的主要構件,要駕馭龐雜又功能強大的GDI,往往也是對程式設計工作的一大考驗。

  由於作業系統不允許應用程式直接存取硬體資源(顯示卡、印表機..等裝置),但視窗應用程式又極需仰賴複雜的圖形,用來顯示與使用者互動的訊息,因此作業系統必須提供繪圖介面供應用程式使用,否則視窗將無法完成繪製圖形以及顯示文字的工作,這也就意味著在視覺的呈現上將會犧牲對使用者的吸引力。

  由於GDI刻意對應用程式隱藏硬體裝置的細節,這也就表示,應用程式的繪圖與顯示文字的需求就必須交由GDI代為與硬體裝置溝通,這種做法可以避免視窗應用程式過度依賴特定裝置的能力,儘管GDI可以代理與裝置溝通的工作,基於使用上的目的, GDI主要是用於處理靜態的圖形輸出,因此僅具有簡單的動畫顯示能力,如果需要產生大量的動畫效果如GAME這類的應用程式,你應該更進一步的尋求DirectX或是 OpenGL這類的函式庫來解決問題。

  不管微軟在GDI的函式庫是如何處理與硬體裝置溝通的細節,你可以簡單的將GDI視為虛擬的繪圖裝置(印表的過程也可以視為是繪圖,只是他的輸出是在印表紙上),透過GDI所提供的函式庫完成工作,這種做法,至少可以滿足二個主要的問題-簡化工作與統一管理。
簡化工作
  在現今的PC上,裝置是由許多的廠商各自所生產的,如果應用程式都需要直接控制硬體,就必須針對各個不同廠牌以及型號單獨撰寫程式碼,而這幾乎是不可能的任務,更糟糕的情況是,以目前硬體進步的速度,可能很多軟體還沒測試出廠,就被淘汰了,對程式設計師而言這將是一場噩夢,因此由作業系統提供一個虛擬裝置的介面,實際控制的部分就由裝置的驅動程式與作業系統溝通,應用程式不需要關心實際硬體的細節,所有低階控制的部分都交由介面負責,這樣相較之下就顯得比較簡化而合理。

統一管理
  由於視窗作業環境在同一時間往往有許多應用程式執行,而這些應用程式的視窗也必須同時輸出在螢幕上(多工作業下的正常狀況),如果每個應用程式都想要掌握硬體的控制權,這時問題就大了,A視窗的作業可能在最大化之後遮蓋整個桌面,而擋住B視窗的資料,或是B視窗的工作會輸出在A視窗之上,這先看似理所當然的工作其實牽涉到許多麻煩的狀況,在不同視窗間的輸出區域可能會增加也可能會減少,甚至使用者移動視窗後,顯示區域也會互相重疊,甚至被破壞,因此必須有一個隨時將顯示區域恢復正常的機制,否則使用者將無法獲得正確的輸出甚至為讓視窗應用程式無法運作,由於GDI是由作業系統提供,在運用GDI函式的同時,也要求應用程式必須相對提出視窗相關資料,因此可以由作業系統判斷何者該更新或是該如何更新。

何謂全域式運算(ROP:raster operation)?當視窗系統用色筆來畫線時,它實際是在色筆的像素與顯示器表面的像素之間執行位元的布林運算。
何謂二元全域式運算(ROP2:binary raster operation)?因為畫線只牽涉兩種像素的圖案,所以布林運算亦稱為如此。

GDI圖形裝置介面 - 視窗系統內的圖形由此來操作的,控制圖形輸出設備的顯示,並在這些驅動檔案中去呼叫常式,如GDI建立可知道顯示器螢幕的.Drv將處理什麼;不處理時則自己將計算畫圖,如畫橢圓。目前都只有普通功能,若要更好的功能,則要借助DirectX及OpenGL。
圖形輸出設備區分為 1.全域式設備(raster),如顯示器介面卡,印表機 及2.向量式設備(vector),如繪圖機。
當要在圖形輸出設備上要畫東西,你就要先取得設備環境代碼,即DC,其如當畫文字時,有文字背景、色彩及字元間隔,而GDI函數的參數只須起始點、文字、文字長度。當釋放DC時即不再有效。
取得特別一個視窗的設備環境1. hdc = BeginPaint (hwnd, &ps); //ps變數是PAINTSTRUCT型態的結構,此結構含有一個叫rcPaint的RECT(矩形)結構,該結構定義一塊能圍住視窗本文工作區無效區域的矩形,利用此傳回的環境代碼,你只能在此區域內畫圖。EndPaint(hwnd, &ps);2. hdc = GetDC(hwnd); //傳回本文工作區的視窗代碼3. hdc = GetWindowDc(hwnd); //而非只是本文工作區,此功能少用。假如你想用,必零去誘捕WM_NCPAINT(非本文北作區著畫)訊息以防止視窗系統在非本文工作區上著畫。ReleaseDC(hwnd, hdc); //釋放DC
取得整個顯示器的設備環境hdc = CreateDC(lpszDriver, lpszDevice, lpszOutput, lpData);// 如hdc = CreateDC('DISPLAY', null, null, null); // 取得一個允許我們在視窗本文工作區之外著畫的設備環境代碼;// 如hdcPrinter = CreateDC('IBMGRX', 'IBM Graphics', 'LPT1:', null); //取得印表機設備環境的代碼Delete(DC);
若只是想取得有關設備環境的資訊,並非想著畫可用hdcInfo = CreateIC(lpszDriver, lpszDevice, lpszOutput, lpData);DeleteDC(hdcInfo);// 如取得記憶體設備環境來操控某些點陣圖hdcMem = CreateCompatibleDC(hdc);DeleteDC(hdcMem);
中介檔案(metafiles),其為寫成二進位形式之GDI呼叫的一些集合,你可用取得中介檔案設備環境來建立中介檔案:hdcMeta = CreateMetaFile(lpszFilename);hmf = CloseMetaFile(hdcMeta);在中介檔案設備環境有效期間,任何你以hdcMeta呼叫的GDI函數都變成中介檔案的一部分。當你呼叫CloseMetafile時,設備環境代碼即為無效。而函數傳回此中介檔案的代碼(hmf)。
取得設備環境資訊如顯示器大小和其色彩的相容性,可呼叫:nValue = GetDeviceCaps(hdc, nIndex);nIndex參數是定義在Windows.H中28個識別字的其中一個。例如, nIndex為HORZRES能使GetDeviceCaps傳回以像素為單位的設備寬度;而VERTRES參數傳回以像素為單位的設備高度。假如hdc是螢幕設備環境的代碼時,你所得到的資訊與從GetSystemMetrics取得的是一樣。假如hdc是印表機的設備環境代碼,則GetDeviceCaps取得以像素為單位的印表機顯示區域之高度和寬度。你可用GetDeviceCaps取得設備處理各類圖形的能力。這點對影像顯示器不重要,但如果是對印表機就很重要。例如, 多數的繪圖機無法畫出點陣圖影像-這點就是由GetDeviceCaps告訴你的。
因為GDI問題在於彩色的點陣圖無法以不同的色彩組織來被儲存,和使用在圖形輸出設備上,固有個新的點陣圖被定義為獨立於設備的點陣圖稱之為DIB,因為其本身的色彩對照表便可指出像素位元如何對應到RGB色彩。

圖形區塊轉移函數有
PatBlt、BitBlt、MaskBlt、PlgBlt、TransparentBlt和StretchBlt等

PatBlt(hdc, xDest, yDest, xWidth, yHeight, dwROP);會用目前選進設備環境中的畫筆

名稱 ROP碼(16進制) 新像素點算法(P來源像素, D目的像素)
BLACKNESS 000042 全部為0
0500A9 ~(P D)
0A0329 ~P & D
0F0001 ~P
500325 ~P & -D
DSTINVERT 550009 ~D
PATINVERT 5A0049 P ^ D
5F00E9 ~(P & D)
A000C9 P & D
A50065 ~(P ^ D)
AA0029 D
AF0229 ~P D
PATCOPY F00021 P
F50225 P ~D
FA0089 P D
WHITENESS FF0062 全部為1

範例如:
procedure TForm1.Button1Click(Sender: TObject);
var
Bitmap: TBitmap;
begin
Bitmap := TBitmap.Create;
try
Bitmap.LoadFromFile('test.Bmp');
Form1.Canvas.Brush.Bitmap := Bitmap;
Form1.Canvas.FillRect(Rect(0,0,100,100));
finally
Form1.Canvas.Brush.Bitmap := nil;
Bitmap.Free;
end;
end;

procedure TForm1.Button2Click(Sender: TObject);
var
Bitmap: TBitmap;
begin
Bitmap := TBitmap.Create;
try
Bitmap.LoadFromFile('test.Bmp');
Form1.Canvas.Brush.Bitmap := Bitmap;
PatBlt(Form1.Canvas.Handle, 0, 0, 100, 100, PATCOPY); //用PATCOPY就等於FillRect
finally
Form1.Canvas.Brush.Bitmap := nil;
Bitmap.Free;
end;
end;

用BitBlt來轉換位元
BitBlt(hdcDest, xDest, yDest, xWidth, yHeight, hdcSrc, xSrc, ySrc, swROP);
名稱 ROP碼(16進制) 新像素點算法(P來源像素, D目的像素)
BLACKNESS 000042 全部為0
NOTSRCERASE 1100A6 ~(S D)
NOTSRCCOPY 330008 ~S
SRCERASE 440328 S & ~D
DSTINVERT 550009 ~D
PATINVERT 5A0049 P ^ D
SRCINVERT 660046 S ^ D
SRCAND 8800C6 S & D
MERGEPAINT BB0226 ~S D
MERGECOPY C000CA P & S
SRCCOPY CC0020 S
SRCPAINT EE0086 S D
PATCOPY F00021 P
PATPAINT FB0A09 P ~S D
WHITENESS FF0062 全部為1

用StretchBlt來擴張Bitmaps
StretchBlt(hdcDest, xDest, yDest, xDestWidth, yDestHeight, hdcSrc, xSrc, ySrc, xSrcWidth, ySrcHeight, dwROP);
當xSrcWidth和xDestWidth值之正負號不相同時,則StretchBlt會建立一個鏡子影像。

當壓縮點陣圖時,StretchBlt必須把數行或數列的像素結合成一行或一列。它根據設備環境中的延伸模式屬性可有三種方法來完成。你還可用SetStretchBltMode函數更改延伸模式:
SetStretchBltMode(hdc, nMode);
nMode之值可為下列之任一個:
BLACKONWHITE(預設值):假如有兩個或更多個像素零被結合成一個像素時,StretchBlt對這些像素執行邏輯AND運算。結果的像素只有在所有原始像素都是白色時才會是白色,這實際意指黑色像素比白色像素更有優勢。
WHITEONBLACK:假如有兩個或更多個像素零被結合成一個像素時,StretchBlt對這些像素執行邏輯OR運算。結果的像素只有在所有原始像素都是黑色時才會是黑色,這實際意指白色像素比黑色像素更有優勢。
COLORONCOLOR:StretchBlt僅把行或列像素除走而不做任何的邏輯運算。對彩色點陣圖而言,因為其他兩種模式會使色彩遭到扭曲,所以通常這是最好的方法。

沒有留言: