<b>17.7 使用shape類</b>
<b></b>
本節介紹圖形庫的一些基本工具:simple_window、window、shape、text、polygon、line、lines、rectangle、function、color、line_style、point、axis。目的是讓你知道這些工具能夠實作什麼功能,而并非詳細了解某個類。下一章将會介紹每個類的設計與實作。
下面來學習一個簡單的程式,我們将逐行解釋代碼,并給出每一行代碼在螢幕上的顯示效果。在程式運作時,你會看到當我們向視窗添加形狀以及改變已有形狀時,螢幕上圖像的變化情況。大體上,我們是通過分析程式的執行情況來“動畫示範”代碼的流程。
17.7.1 圖形頭檔案和主函數
首先,我們包含定義了圖形和gui工具接口的頭檔案:
或者
你可能已經猜到,window.h包含與視窗有關的工具,graph.h包含在視窗上繪制形狀(包括文本)的有關工具,這些工具都定義在graph_lib名字空間中。為簡化起見,我們使用名字空間指令,使得graph_lib中的名字可以直接在程式中使用。
照例,main()函數包含我們要(直接或間接)執行的代碼及例外處理:
main()函數進行編譯時,必須已定義了exception。如果我們照例包含了std_lib_facilities.h,就會得到exception,否則我們會從标準頭檔案處開始直接處理,此時需要包含<stdexcept>。
17.7.2 一個幾乎空白的視窗
在這裡,我們不讨論錯誤處理(參見第5章,特别是5.6.3節),直接進入main()函數中的圖形代碼:
這段代碼首先建立一個simple_window,即一個有“next”按鈕的視窗,并将它顯示在螢幕上。顯然,為了得到simple_window對象,我們應該包含頭檔案simple_window.h而不是window.h。在這裡,我們明确給出了視窗在螢幕上的顯示位置:其左上角位于point{100, 100}。這個位置很接近螢幕的左上角,但沒有過于靠近。很顯然,point是一個類,其構造函數有兩個整型參數,表示點在螢幕上的(x, y)坐标。我們可以将代碼寫為:
然而,為了便于多次使用點(100, 100),我們還是選擇給它一個符号名稱。600和400分别是視窗的寬度和高度,canvas是在視窗框上顯示的标簽。
為了真正将視窗繪制在螢幕上,我們必須将控制權交給gui系統。我們通過調用win.wait_for_button()來達到這一目的,結果如下:
在視窗的背景中,我們看到了一個筆記本電腦的桌面(已經臨時清理過了)。如果你對桌面背景這種不相關的事情感到好奇,我可以告訴你,我拍攝照片時正站在安提布的畢加索資料館附近俯瞰尼斯灣。隐藏在程式視窗之後的黑色控制台視窗是用來運作我們的程式的。控制台視窗不太美觀,而且也不是必需的,但當一個尚未調試通過的程式陷入無限循環或無法繼續執行時,我們可以通過它來終止程式。如果你仔細觀察,會發現我們使用的是微軟c++編譯器,當然你可以使用其他的編譯器(如borland或者gnu)。
在之後的介紹中,我們将去掉程式視窗周圍分散注意力的内容,僅僅給出視窗本身:
視窗的實際尺寸(以像素計算)依賴于螢幕的分辨率。某些螢幕的像素要比其他螢幕更大。
17.7.3 坐标軸
一個幾乎空白的視窗沒有什麼意思,最好給它添加一些資訊。希望添加些什麼内容呢?注意:并不是所有的圖形都是有趣的或者是關于遊戲的,我們将從坐标軸——一種嚴肅的、有點複雜的圖形開始。一個沒有坐标軸的圖形通常是很難看的。沒有坐标軸的幫助,我們通常難以弄清資料的含義。或許你可以借助伴随的文字來解釋,但使用坐标軸要保險得多;人們通常不會閱讀文字描述,而且好的圖形表示通常與其語境是分離的。是以,圖形需要坐标軸:
操作步驟為:建立坐标軸對象,将其添加到視窗,最後進行顯示:
可以看到,axis::x是一條水準線,其上有指定數量的“刻度”(10個)和一個标簽“x axis”。通常,标簽用于解釋坐标軸和刻度的含義。我們通常把x軸放在視窗底端附近。在實際應用中,我們更喜歡用符号常量來表示高度和寬度,這樣“在底端上方附近”就可以用y_max-bottom_margin這樣的符号表示,而不是用300這樣的“魔數”(參見4.3.1節和20.6.2節)。
為了幫助識别程式的輸出,我們用window的成員函數set_label()将該視窗的标簽重新設定為“canvas #2”。
現在,添加一個y坐标軸:
我們将y軸和标簽的顔色分别設定為cyan和dark_red(隻是為了展示一些工具的使用)。
我們并不認為x軸和y軸使用不同顔色是一個好主意。這裡隻是為了說明如何設定形狀或者其中某個元素的顔色。使用很多種顔色未必是個好主意,特别是初學者更容易熱衷使用很多顔色。
17.7.4 繪制函數圖
接下來做什麼呢?現在,我們已經有了一個包含坐标軸的視窗,是以看起來畫出一個函數是個好主意。我們建立一個形狀來表示正弦函數,并将它添加到視窗:
在這段代碼中,名為sine的function對象使用标準庫函數sin()産生的值繪制一條正弦曲線。我們将在20.3節詳細讨論如何繪制函數圖。現在,你隻需知道,繪制函數圖時必須給出起始點的位置和輸入值集合(值域),并且還需給出一些資訊來說明如何将這些内容塞入視窗(縮放)。
請注意在到達視窗右邊界時曲線是如何停止的。當我們繪制的點超出視窗矩形區域時,将被gui系統簡單忽略掉,永遠不會真正顯示出來。
17.7.5 polygon
函數圖是表示資料的一種方法,在第20章将會看到更多執行個體。我們還可以在視窗中繪制不同類型的對象:幾何形狀。我們使用幾何形狀來進行圖形示範,可以表示使用者互動元件(如按鈕),通常還能使示範更加生動。多邊形(polygon)被描述為一個點的序列,這些點通過線連接配接起來就構成polygon類。第一條線連接配接第一個點到第二個點,第二條線連接配接第二個點到第三個點,以此類推,最後一條線連接配接最後一個點到第一個點。
這段代碼首先展示了如何改變正弦曲線的顔色。然後,與17.3節的例子一樣,我們添加了一個三角形,作為一個多邊形的例子。然後我們再次設定了顔色,最後設定了線型。polygon的線都有“線型”,預設線型為實線,但我們也可根據需要改為虛線、點狀線等(參見18.5節)。這段程式顯示如下圖形:
17.7.6 rectangle
螢幕是矩形,視窗是矩形,一張紙也是一個矩形。實際上,現實世界中有很多形狀都是矩形(至少是圓角矩形)。原因在于矩形是最容易處理的形狀。例如:矩形易于描述(左上角和寬度、高度,或者左上角和右下角,諸如此類),易于判斷一個點在矩形之内還是之外,易于用硬體快速繪制像素構成的矩形。
與其他封閉的形狀相比,大多數進階圖形庫能夠更好地處理矩形。是以,我們将矩形類rectangle從多邊形類polygon中獨立出來。一個rectangle可以用左上角坐标、寬度和高度來描述:
由此可得:
請注意,将位置正确的四個點連接配接起來并不一定得到一個rectangle。當然,在螢幕上建立一個看起來像rectangle的closed_polyline是很簡單的(你甚至可以建立一個看起來像是rectangle的open_polyline),例如:
實際上,poly_rect對應的螢幕圖像(image)是一個矩形。但記憶體中的poly_rect對象并不是一個rectangle對象,而且它也不“知道”有關矩形的任何内容。驗證這一點的最簡單方法是再添加一個點:
矩形是不會有5個點的:
對于我們分析程式非常重要的一點是:rectangle不僅僅是在螢幕上看起來像是一個矩形而已,它還應該從根本上保證此形狀(幾何意義上)始終是一個矩形。這樣,我們編寫代碼時就可以信賴rectangle——它确實表示螢幕上的一個矩形,而且保證不會改變為其他形狀。
17.7.7 填充
前面繪制形狀都是繪制輪廓,我們也可以使用某種顔色“填充”一個矩形:
我們還覺得對三角形(poly)目前的線型不滿意,是以将其線型設定為“粗(正常線型的4倍粗細)虛線”,我們也改變了poly_rect(現在已經看起來不像是矩形了)的線型。
如果你仔細觀察poly_rect,你會發現輪廓是在填充色上層顯示的。
任何封閉的形狀都可以被填充(參見18.9節)。矩形很特殊,它非常容易填充(填充速度也非常快)。
17.7.8 text
最後,任何一個繪圖系統都不可能完全沒有簡單的文本輸出方式——将每個字元看作線的集合來繪制,并保證不會剪切掉字元。我們已經展示了如何為視窗和坐标軸設定标簽,但我們也能使用text對象将文本放置在任何位置。
利用此例中的基本圖形元素,你可以生成任何複雜、微妙的顯示效果。請注意本章所有代碼的一個共同特點:沒有循環和選擇語句,而且所有資料都是“寫死的”。輸出内容隻是基本圖形元素的簡單組合。一旦我們開始使用資料和算法來組合這些基本圖形,就可以得到更複雜、更有趣的輸出效果了。
我們已經看到過如何改變文本的顔色了:坐标軸的标簽(參見17.7.3節)本身就是一個text對象。此外,我們還可以為文本選擇字型和字号:
這段代碼将text的字元串“hello, graphical world!”中的字元放大到20号字,字型設定為粗體times。
17.7.9 image
我們還可以從檔案中加載圖檔:
執行上述代碼,将在視窗中顯示檔案名為image.jpg的照片,照片中兩架飛機正在突破音障:
這幅照片比較大,我們剛好把它放在了文本和圖形上層。是以,為了清理視窗,我們将它稍微移開一點:
請注意不在視窗區域之内的部分圖檔是如何被簡單忽略掉的。超出視窗區域的内容都會這樣被圖形系統“剪裁”掉。
17.7.10 更多未讨論的内容
下面代碼展示了圖形庫更多的特性,在這裡不再進行詳細解釋:
你能猜出這段代碼顯示什麼内容嗎?是不是很容易猜?
代碼與螢幕顯示内容的關聯是很直接的。如果你還未看出這段代碼是如何産生這樣的輸出的,請繼續學習後續章節,很快就會搞清楚的。請注意我們是如何使用ostringstream(參見11.4節)來格式化輸出尺寸的文本對象的。