## 1.6 圖形類 Graphics
與 GDI 的 MFC 類 CDC 類似,GDI+的繪圖功能主要由圖形類 Graphics 承擔。
圖形類 Graphics 是 GDI+的核心,它提供繪制圖形、圖像和文本的各種方法(似 GDI 中的 CDC 類),還可以存儲顯示設備和被畫項目的屬性(到圖元文件)。Graphics 類及其方 法都被定義在頭文件 Gdiplusgraphics.h 中。


圖 14-12 顏色枚舉常量
### 1.6.1 構造函數
Graphics 類的構造函數有如下 4 種:
```
Graphics(Image* image); // 用于繪制圖像
Graphics(HDC hdc); // 用于在當前窗口中繪圖
Graphics(HDC hdc, HANDLE hdevice); // 用于在制定設備上繪制圖形
Graphics(HWND hwnd, BOOL icm = FALSE); // 用于在指定窗口中繪圖
```
其中,最常用的是第二種——在當前視圖窗口中繪圖的圖形類構造函數。
注意,該構造函數的輸入參數,是設備上下文的句柄,而不是 CDC 類對象的指針。一 般可以由 CDC 對象得到(因 CDC 類含有公用數據成員 HDC m_hDC;):
+ 在 OnDraw 函數中,利用輸入參數 CDC *pDC,就可直接得到 DC 句柄。例如:
```
Graphics graph(pDC->m_hDC);
```
+ 在視圖類的其他函數中,可先利用 GetDC 函數得到 CDC 指針,然后再利用它去獲 取 DC 的句柄。例如:
```
Graphics graph(GetDC()->m_hDC);
```
也可以使用 Graphics 類的另一個構造函數 `Graphics(HWND hwnd, BOOL icm = FALSE);`,利用視圖類的窗口句柄成員來構造 Graphics 對象。例如:
```
Graphics graph(this->m_hWnd);
```
### 1.6.2 狀態枚舉 status
在圖形類 Graphics 中,封裝了各種繪圖方法。每種繪圖方法被調用后,都會返回一種 叫做 status 的枚舉值,反映該方法是否被正確執行,0 表示正確,其他大于 0 的值為錯誤代 碼(GdiplusTypes.h):
```
typedef enum { // 狀態枚舉(含 22 個枚舉值)
Ok = 0,
GenericError = 1,
InvalidParameter = 2,
OutOfMemory = 3,
...
PropertyNotSupported = 20,
ProfileNotFound = 21
} Status;
```
GDI+的繪圖功能被封裝在圖形類 Graphics 中,下面介紹其中的常用繪圖方法。先講繪 制線型圖的方法,再講繪制填充圖的方法,最后講繪制文字的方法。
### 1.6.3 畫線型圖的方法
GDI+中繪制線型圖形的方法與 GDI 的類似,也包括繪直線、矩形、橢圓和多邊形等, 但是 GDI+增加了浮點版本和若干新功能。GDI+的畫線函數都是 Graphics 類的方法,而且所 有方法的名稱都是以 Draw 開頭。
(1)畫直線[折線]DrawLine[s]
在 GDI+中定義了 6 種繪制直線和折線的方法,前三個為整數版,后三個為對應的浮點 數版:
```
Status DrawLine(const Pen* pen, INT x1, INT y1, INT x2, INT y2);
Status DrawLine(const Pen* pen, const Point& pt1, const Point& pt2);
Status DrawLines(const Pen* pen, const Point* points, INT count);
Status DrawLine(const Pen* pen, REAL x1, REAL y1, REAL x2, REAL y2);
Status DrawLine(const Pen* pen, const PointF& pt1, const PointF& pt2);
Status DrawLines(const Pen* pen, const PointF* points, INT count);
```
其中:
+ DrawLine——畫直線(4 個重載),參數 pen 為畫直線所用的筆、(x1, y1)和 pt1 為 直線的起點、(x2, y2)和 pt2 為直線的終點。GDI 的相應函數為 MoveTo 和 LineTo。
+ DrawLines——畫折線(一串相互連接的直線段)(2 個重載),參數 points 為點數 組、count 為數組中點的數目。GDI 的相應函數為 Polyline。
(2)畫矩形[組] DrawRectangle[s]
在 GDI+中也定義了 6 種繪制矩形和矩形組的方法,也是前三個為整數版,后三個為對 應的浮點數版:
```
Status DrawRectangle(const Pen* pen, const Rect& rect);
Status DrawRectangle(const Pen* pen, INT x, INT y, INT width, INT height);
Status DrawRectangles(const Pen* pen, const Rect* rects, INT count);
Status DrawRectangle(const Pen* pen, const RectF& rect);
Status DrawRectangle(const Pen* pen, REAL x, REAL y, REAL width, REAL height);
Status DrawRectangles(const Pen* pen, const RectF* rects, INT count);
```
其中:
+ DrawRectangle——畫單個矩形(4 個重載),參數 pen 為畫矩形所用的筆、rect 為 矩形區域、(x, y)為矩形的左上角、(width, height)為矩形的大小(寬,高)。與 GDI 的對應函數 BOOL Rectangle( int x1, int y1, int x2, int y2);的區別主要是 GDI+的第 2 個和第 4 個畫矩形方法的后兩個輸入參數,不再是 GDI 中的矩形右下角的坐標, 而改成矩形的寬和高了。
+ DrawRectangles——畫多個矩形(2 個重載),參數 rects 為矩形數組、count 為數組 中矩形的數目。GDI 中沒有同時繪制一個矩形數組的函數。
(3)[橢]圓 DrawEllipse
GDI+中有 4 個重載的繪制橢圓的方法,如果輸入參數所確定的外接矩形的寬高相等, 則畫圓。也是前兩個為整數版,后兩個為對應的浮點數版:
```
Status DrawEllipse(const Pen* pen, const Rect& rect);
Status DrawEllipse(const Pen* pen, INT x, INT y, INT width, INT height) Status DrawEllipse(const Pen* pen, const RectF& rect);
Status DrawEllipse(const Pen* pen, REAL x, REAL y, REAL width, REAL height);
```
這些方法的功能,與 GDI 中的函數:
```
BOOL Ellipse( int x1, int y1, int x2, int y2 );
```
圖 14-13 畫弧方法的輸入參數
類似,但是同樣要注意 GDI+的 DrawEllipse 方法與 GDI 的 Ellipse 函數的主要區別(與畫矩 形的方法與函數類似),是上面的以坐標為參數的第 2、4 個 GDI+畫橢圓方法的后兩個輸入 參數,也是矩形的寬高而不再是矩形的右下角坐標了。
(4)畫[橢]圓弧 DrawArc
GDI+中也有 4 個重載的繪制橢圓弧的方法,如果輸入參數所確定的外接矩形的寬高相 等,則畫圓弧。也是前兩個為整數版,后兩個為對應的浮點數版。

```
Status DrawArc(const Pen* pen, INT x, INT y, INT width, INT height, REAL startAngle, REAL sweepAngle);
Status DrawArc(const Pen* pen, const Rect& rect, REAL startAngle, REAL sweepAngle);
Status DrawArc(const Pen* pen, REAL x, REAL y, REAL width, REAL height, REAL startAngle, REAL sweepAngle);
Status DrawArc(const Pen* pen, const RectF& rect, REAL startAngle, REAL sweepAngle);
```
注意,角度的單位是度(不是弧度,C++的三角函數 采用的是弧度單位),而且都必須是實數。零度角為 x 軸 方向,順時針方向為正(這與數學上反時針方向為正剛好相反),參見圖 14-13。
(5)畫多邊形 DrawPolygon
GDI+中有 2 個重載的繪制多邊形的方法,前一個為整數版,后一個為對應的浮點數版:
```
Status DrawPolygon(const Pen* pen, const Point* points, INT count);
Status DrawPolygon(const Pen* pen, const PointF* points, INT count);
```
其中,各參數的含義同畫折線方法 DrawLines 的,只是 DrawPolygon 方法會將點數組中的起點和終點連接起來,形成一個封閉的多邊形區域。 該方法的功能與 GDI 的 Polygon 函數相同:
```
BOOL Polygon( LPPOINT lpPoints, int nCount );
```
注意:GDI+中沒有提供與 GDI 函數 RoundRect(圓角矩形)和 Chord(弓弦)具有類 似功能的繪圖方法,但可以利用矩形+橢圓和弧+直線等方法來自己實現。
### 1.6.4 畫填充圖的方法
在 GDI 中,任何畫封閉區域的性狀圖繪制函數(如矩形、圓角矩形、[橢]圓、弓弦和多 邊形等),都可以畫填充圖,因為它們總是在用當前筆畫指定邊框的同時,也用當前刷子填 充內部區域。
而 GDI+的畫線方法就沒有這個功能,因為在 GDI+是無狀態的,沒有當前筆和刷的概 念。為了完成與這些 GDI 函數類似的功能,在 GDI+中,你得分兩步來做:先用填充方法填 充區域內部,再用畫線方法繪制邊框。
在 GDI+中畫填充圖,不需像 GDI 那樣得先將刷子選入 DC,而是與 GDI+畫線狀圖的 方法類似,將刷子作為畫填充圖方法的第一個輸入參數。注意,GDI+中的畫填充圖的方法 都以 Fill 開頭。
(1) 畫填充矩形[組]FillRectangle[s]
GDI+中有 6 個重載的繪制填充矩形[組]的方法,前 3 個為整數版,后 3 個為對應的浮點 數版:
```
Status FillRectangle(const Brush* brush, const Rect& rect);
Status FillRectangle(const Brush* brush, INT x, INT y, INT width, INT height);
Status FillRectangles(const Brush* brush, const Rect* rects, INT count);
Status FillRectangle(const Brush* brush, const RectF& rect);
Status FillRectangle(const Brush* brush, REAL x, REAL y, REAL width, REAL height);
Status FillRectangles(const Brush* brush, const RectF* rects, INT count);
```
用指定刷子 Brush,填充 rect 的內部區域,無邊線,填充區域包括矩形的左邊界和上邊 界,但不包括矩形的右邊界和下邊界。功能與 GDI 的 FillRect 函數類似:
```
void FillRect( LPCRECT lpRect, CBrush* pBrush );
```
但是,GDI 中沒有同時填充一個矩形數組的函數。不過 GDI 卻有 GDI+中所沒有的畫填充圓 角矩形的函數 FillSolidRect。
(2) 畫填充橢圓 FillEllipse
GDI+中有 4 個重載的繪制填充橢圓的方法,前 2 個為整數版,后 2 個為浮點數版:
```
Status FillEllipse(const Brush* brush, const Rect& rect);
Status FillEllipse(const Brush* brush, INT x, INT y, INT width, INT height);
Status FillEllipse(const Brush* brush, const RectF& rect);
Status FillEllipse(const Brush* brush, REAL x, REAL y, REAL width, REAL height);
```
GDI 中沒有類似函數,但可以用(采用當前刷填充的)Ellipse 函數來代替。
(3) 畫餅圖 DrawPie
GDI+中有 4 個重載的繪制餅圖的方法,前 2 個為整數版,后 2 個為浮點數版:
```
Status DrawPie(const Pen* pen, const Rect& rect, REAL startAngle, REAL sweepAngle);
Status DrawPie(const Pen* pen, INT x, INT y, INT width, INT height, REAL startAngle, REAL sweepAngle);
Status DrawPie(const Pen* pen, const RectF& rect, REAL startAngle, REAL sweepAngle);
Status DrawPie(const Pen* pen, REAL x, REAL y, REAL width, REAL height, REAL startAngle, REAL sweepAngle);
```
與 GDI 的下列函數類似,但是部分輸入參數的含義有所不同:
```
BOOL Pie( int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4 );
BOOL Pie( LPCRECT lpRect, POINT ptStart, POINT ptEnd );
```
例如(參見圖 14-14):

```
void DrawPies(Graphics &graph, const Color cols[], Point &O, int r, const float data[], int n) {
Rect rect(O.X - r, O.Y - r, 2 * r, 2 * r);
float startAngle = 0, sweepAngle;
for (int i = 0; i < n; i++) {
sweepAngle = data[i] * 360.0f;
graph.FillPie(&SolidBrush(cols[i]), rect, startAngle, sweepAngle);
startAngle += sweepAngle;
}
}
void CGdipDrawView::OnDraw(CDC* pDC) {
……
Graphics graph(pDC->m_hDC);
Color cols[] = {Color::Red, Color::Green, Color::Blue, Color::Aqua};
float data[] = {0.2f, 0.4f, 0.1f, 0.3f};
DrawPies(graph, cols, Point(200, 200), 100, data, 4);
……
}
```
(4) 畫填充多邊形 FillPolygon
GDI+中有 4 個重載的繪制填充多邊形的方法,前 2 個為整數版,后 2 個為浮點數版:
```
Status FillPolygonconst Brush* brush, const Point* points, INT count);
Status FillPolygon(const Brush* brush, const Point* points, INT count, FillMode fillMode);
Status FillPolygon(const Brush* brush, const PointF* points, INT count);
Status FillPolygon(const Brush* brush, const PointF* points, INT count, FillMode fillMode);
```
其中,填充模式參數 FillMode,可取如下兩個值之一(參見 8.5.3 中的 1.):
```
typedef enum {
FillModeAlternate, // 交替模式——按奇偶規則填充(默認模式)
FillModeWinding // 環繞模式——按非零環繞規則填充
} FillMode;
```
對簡單圖形,這兩種模式的效果是一樣的,但對復雜圖形,特別是有穿插的圖,結果可 能是不同的。例如(畫五角星,參見圖 14-15):
```
// 定義五角星頂點數組
const int n = 5;
Point p1(100, 0);
Point p2(195, 69);
Point p3(159, 181);
Point p4(41, 181); Point p5(5, 69);
Point ps0[n] = {p1, p2, p3, p4, p5};
Point ps[n] = {p1, p3, p5, p2, p4};
// 創建實心刷對象
SolidBrush redBrush(Color(128, 0, 0));
SolidBrush greenBrush(Color(0, 128, 0));
SolidBrush blueBrush(Color(0, 0, 128));
// 畫五角星
Graphics graph(pDC->m_hDC);
graph.DrawPolygon(&Pen(Color::Red), ps0, n);
graph.DrawPolygon(&Pen(Color::Green), ps, n);
// 畫填充五角星
graph.TranslateTransform(200, 0); // 右移 200 像素
graph.FillPolygon(&redBrush, ps0, n);
graph.TranslateTransform(200, 0);
graph.FillPolygon(&greenBrush, ps, n, FillModeAlternate);
graph.TranslateTransform(200, 0);
graph.FillPolygon(&blueBrush, ps, n, FillModeWinding);
```

多邊形 交替/環繞模式 交替模式 環繞模式
圖 14-15 填充多邊形(五角星)
GDI 中也沒有與畫填充多邊形類似的專門函數,但可以用(采用當前刷填充的)Polygon 來代替。
### 1.6.5 畫曲線的方法
前面講的各種畫線狀圖或填充圖的 GDI+方法,雖然在形式上與 GDI 的有所不同(方法 名前加了 Draw 或 Fill、將筆或刷作為第一個輸入參數、部分輸的位置入參數改成了大小參 數、并增加了浮點數版),但是在功能上卻是相同的。
現在要講的曲線繪制,則是 GDI+新增加的內容。曲線在機械設計、工程建筑和圖形動 畫等領域,都有十分廣泛應用。
常用的曲線有 Bezier(貝塞爾)曲線和樣條(spline)曲線。貝塞爾曲線比較簡單,適 合于畫控制點少的曲線。當控制點太多時,要不曲線的次數(比點數少 1)太高,要不拼接 比較困難,而且沒有局部性(即修改一點影響全局),性能不太好。而樣條曲線則可以畫任 意多個控制點的曲線,曲線的次數也可以指定(一般為二次或三次),并且具有局部性。貝 塞爾曲線特別是樣條曲線有很多變種。常見的貝塞爾曲線有普通貝塞爾曲線和有理貝塞爾曲 線。常用的樣條曲線有:B 樣條、β 樣條、Hermite(厄密)樣條、基樣條(cardinal splines)、 Kochanek- Bartels 樣條和 Catmull-Rom 樣條等。
GDI+中所實現的是普通貝塞爾曲線(不過控制點,位于控制多邊形的凸包之內)和基 樣條曲線(過控制點)。有關曲線和曲面構造方法,會在課程《計算機圖形學》中介紹。
(1)基樣條曲線(cardinal spline curve)
```
Status DrawCurve(const Pen* pen, const Point* points, INT count, REAL tension = 0.5f);
Status DrawCurve(const Pen* pen, const PointF* points, INT count, REAL tension = 0.5f);
Status DrawClosedCurve(const Pen *pen, const Point* points, INT count, REAL tension = 0.5f);
Status DrawClosedCurve(const Pen *pen, const PointF* points, INT count, REAL tension = 0.5f);
```
其中:
+ 參數 tension(張力)指定曲線的彎曲程度,tension = 0.0(直線)~1.0(最彎曲)。
+ DrawClosedCurve 方法(連接首尾點)畫封閉的基樣條曲線。 例如(參見圖 14-16):
```
void DrawPoints(Graphics &graph, const Color &col, int r, const Point* points, INT count) {
// 自定義的畫點列函數 SolidBrush brush(col);
for (int i = 0; i < count; i++)
graph.FillEllipse(&brush, Rect(points[i].X - r,
points[i].Y - r, 2 * r, 2 * r));
}
Graphics graph(pDC->m_hDC);
// 定義 Pen 對象和 Point 對象的數組
Pen greenPen(Color::Green, 3);
Point p1(10, 100), p2(100, 50), p3(300, 10), p4(400, 100);
Point ps[4] = {p1, p2, p3, p4};
// 繪制不同張力的基樣條曲線
graph.DrawCurve(&Pen(Color::Magenta), ps, 4, 1.0);
graph.DrawCurve(&greenPen, ps, 4, 0.5);
graph.DrawCurve(&Pen(Color::Blue), ps, 4, 0.0);
DrawPoints(graph, Color::Red, 5, ps, 4); // 繪制曲線的控制點
// 繪制默認張力的基樣條、封閉基樣條與貝塞爾曲線
graph.TranslateTransform(450, 0); // 水平右移 450 個像素
graph.DrawCurve(&greenPen, ps, 4);
graph.DrawClosedCurve(&Pen(Color::Aqua), ps, 4);
graph.DrawBeziers(&Pen(Color::Chocolate), ps, 4);
DrawPoints(graph, Color::Red, 5, ps, 4); // 繪制曲線的控制點
```

不同張力的基樣條曲線 基樣條、封閉基樣條與貝塞爾曲線
圖 14-16 基樣條曲線與貝塞爾曲線
(2)貝塞爾曲線(Bezier curve)
```
Status DrawBezier(const Pen* pen, INT x1, INT y1, INT x2, INT y2, INT x3, INT y3, INT x4, INT y4);
Status DrawBezier(const Pen* pen, const Point& pt1, const Point& pt2, const Point& pt3, const Point& pt4);
Status DrawBeziers(const Pen* pen, const Point* points, INT count);
... // 對應的浮點版本
```
(3)填充封閉基樣條曲線
```
Status FillClosedCurve(const Brush* brush, const Point* points, INT count);
Status FillClosedCurve(const Brush* brush, const Point* points, INT count, FillMode fillMode, REAL tension = 0.5f);
... // 對應的浮點版本
```
例如,將前面畫圖 14-15 所對應的填充多邊形例子中的畫填充五角星的三個語句中的 FillPolygon 方法,改為填充封閉基樣條曲線方法 FillClosedCurve,結果如圖 14-17 所示。

多邊形 交替/環繞模式 交替模式 環繞模式
圖 14-17 填充閉曲線
### 1.6.6 平滑處理
可以利用 Graphics 類的設置平滑模式方法
```
Status SetSmoothingMode(SmoothingMode smoothingMode);
```
來設置繪圖時的平滑化處理。其中的輸入參數為枚舉類型:
```
typedef enum {
SmoothingModeInvalid = QualityModeInvalid, //無效(保留)
SmoothingModeDefault = QualityModeDefault, // 默認(低質,無平滑處理)
SmoothingModeHighSpeed = QualityModeLow, // 高速(低質,無平滑處理)
SmoothingModeHighQuality = QualityModeHigh, // 高質(使用 8*4 盒過濾器)
SmoothingModeNone, // 無平滑處理
SmoothingModeAntiAlias8x4, // 使用 8*4 盒過濾器(庫中無)
SmoothingModeAntiAlias =
SmoothingModeAntiAlias8x4, // 使用 8*4 盒過濾器
SmoothingModeAntiAlias8x8 // 使用 8*8 盒過濾器(最高質,庫中也無)
} SmoothingMode;
```

圖 14-18 平滑處理
例如(參見圖 14-18):
```
Graphics graph(pDC->m_hDC);
Pen pen(Color::Black, 4);
Rect rect(10, 10, 200, 200);
graph.DrawRectangle(&pen, rect);
graph.RotateTransform(1);
graph.TranslateTransform(20, 20);
//graph.SetSmoothingMode(SmoothingModeNone);
graph.DrawRectangle(&pen, rect);
graph.TranslateTransform(20, 20);
graph.SetSmoothingMode(SmoothingModeAntiAlias);
graph.DrawRectangle(&pen, rect);
```
### 1.6.7 清屏方法 Clear
GDI 中沒有用于清屏的專門函數,得自己用背景色畫窗口大小的填充矩形,或者調用窗 口類的 Invalidate 和 UpdateWindow 函數。現在,GDI+有了清屏方法 Clear:
```
Status Clear(const Color &color);
```
其中的輸入參數 color,為用戶指定的填充背景色。例如:
```
Graphics graph(GetDC()->m_hDC);
……
graph.Clear(Color::White);
```