## 1.7 笔和刷
本节介绍 GDI+的两类绘图工具——笔和刷,它们与 GDI 的相比新增加了许多功能。
### 1.7.1 笔
与 GDI 中的一样,GDI+中的笔(pen)也是画线状图的工具,但是功能更加强大。例 如:透明笔、图案笔、自定义虚线风格、线帽、笔的缩放和旋转、笔的连接点属性等。
GDI+中的笔对应于 Pen 类,被定义在 GdiplusPen.h 头文件中。 笔的构造方法主要有两个:
```
Pen(const Color &color, REAL width = 1.0); // 单色笔
Pen(const Brush *brush, REAL width = 1.0); // 纹理图案笔
```
其中,最常用的是第一个,它构造一个颜色为 color,宽度为 width(默认为 1)的单色笔。 如果颜色的α 值<255,则所创建的笔就是带透明度的笔。
(1)笔对齐
当笔宽大于 1 时,默认情况下,是以笔的中心与绘图坐标对齐。但是,也可以采用 Pen类的方法:
```
Status SetAlignment(PenAlignment penAlignment);
```
设置为内对齐,其输入参数取枚举类型 PenAlignment 的符号常量:
```
typedef enum {
PenAlignmentCenter = 0, // 中心对齐(默认值)
PenAlignmentInset = 1 // 内对齐
} PenAlignment;
```
例如(输出结果如图 14-19 所示):
```
Graphics graph(pDC->m_hDC);
Rect rect(20, 20, 300, 200);
Pen pen(Color::Green, 30), redPen(Color::Red);
graph.DrawEllipse(&pen, rect);
graph.DrawRectangle(&redPen, rect);
pen.SetAlignment(PenAlignmentInset);
graph.TranslateTransform(340, 0);
graph.DrawEllipse(&pen, rect);
graph.DrawRectangle(&redPen, rect);
```
![image](https://box.kancloud.cn/2016-04-18_57144a7ebf949.jpg)
a) 中心对齐(默认值) b) 内对齐
图 14-19 笔对齐
(2)图案笔
笔类 Pen 的第二个构造方法,是从刷子来创建笔,如果是单色的实心刷,则相当于第一 个笔构造方法。如果刷子为条纹(影线)或纹理(图像)等图案刷,则该构造函数所创见的 就是对应的图案笔。
例如(条纹笔画椭圆,参见图 14-20):
```
HatchBrush hBrush(HatchStyleCross, Color::Green, Color::Red); // 创建十字线条纹刷
Pen hPen(&**hBrush**, 40); // 创建宽度为 40 像素的条纹笔
graph.DrawEllipse(&hPen, 20, 20, 400, 250); // 画椭圆
```
![image](https://box.kancloud.cn/2016-04-18_57144a7ed3842.jpg)
图 14-20 条纹笔椭圆
![image](https://box.kancloud.cn/2016-04-18_57144a7ee4817.jpg)
图 14-21 纹理笔椭圆
又例如(纹理笔画椭圆,参见图 14-21):
```
Image img(L"张东健.bmp"); // 创建图像对象,并装入图像文件
TextureBrush tBrush(&img); // 创建纹理刷
Pen tPen(&**tBrush**, 80); // 创建宽度为 80 像素的纹理笔
Graphics graph(GetDC()->m_hDC); // 创建图形对象
graph.DrawEllipse(&tPen, 40, 40, 640, 400); // 画椭圆
```
(3)线型
与 GDI 一样,对 GDI+中的笔,也可以设置线型。所用的方法为:
```
Status SetDashStyle(DashStyle dashStyle);
```
其中的输入参数,为虚线风格枚举 DashStyle:(GdiplusEnums.h)
```
enum DashStyle {
DashStyleSolid, // 0 实线 (默认值)
DashStyleDash, // 1 虚线
DashStyleDot, // 2 点线:
DashStyleDashDot, // 3 虚点线:
DashStyleDashDotDot, // 4 虚点点线
DashStyleCustom // 5 自定义虚线
};
```
![](https://box.kancloud.cn/2016-04-18_57144a7f0a7b3.png)
可以用 Pen 类的另一个方法来获取笔的线型:
```
DashStyle GetDashStyle() const;
```
GDI+中的线型,大多数与 GDI 中的相同,区别主要有两点:
+ GDI 中的非实线线型,对宽度>1 的笔无效;而 GDI+的笔对任意非零宽度的笔都是有效的。
+ GDI+中新增了一种风格——自定义虚线风格。 具体的自定义虚线风格,由 Pen 类的设置虚线图案的方法
```
Status SetDashPattern(const REAL *dashArray, INT count);
```
来设置,其中的实数数组 dashArray 含若干个正实数(单位为像素),按线、空、线、空、„„ 的交叉方式排列;参数 count 为数组中实数的个数(须>0)。
例如(参见图 14-22):
```
Graphics graph(pDC->m_hDC);
Pen pen(Color::Black, 8); // 创建宽 8 个像素的黑色笔(画虚线用)
// 线 5、空 2、线 15、空 4(像素)
REAL dashVals[4] = {5.0f, 2.0f, 15.0f, 4.0f};
FontFamily fontFamily(L"Times New Roman"); // 创建字体族对象
// 创建 5 号字大小的 Times New Roman 字体
Font font(&fontFamily, 10.5);
// 创建绿色的实心刷(写字符串用)
SolidBrush brush(Color(0, 128, 0));
// 笔的虚线风格枚举常量的名称字符串数组
CString strs[] = {L"DashStyleSolid", L"DashStyleDash", L"DashStyleDot", L"DashStyleDashDot", L"DashStyleDashDotDot", L"DashStyleCustom"};
for (int i = 0; i <= 5; i++) { // 绘制各种风格的虚线及其名称串
pen.**SetDashStyle**((DashStyle)i); // 设置笔的虚线风格
// 设置自定义虚线图案
if (i == 5) pen.SetDashPattern(dashVals, 4);
// 画虚线
graph.DrawLine(&pen, 10, 10 + i * 20, 400, 10 + i * 20);
// 绘制虚线风格枚举常量名称字符串
graph.DrawString(strs[i], -1, &font,
PointF(410, 2 + i * 20), &brush);
}
```
![image](https://box.kancloud.cn/2016-04-18_57144a7f22f0b.jpg)
图 14-22 虚线风格
还可以用 Pen 类的另一个方法来获取笔的自定义虚线图案数据:
```
INT GetDashPatternCount(VOID); // 获取虚线数组中实数的个数
Status GetDashPattern(REAL *dashArray, INT count); // 获取虚线数组
```
(4)线帽
线帽(line cap)是指线条两端的外观,默认为正方形,也可以用 Pen 类的下列方法来 设置不同的线端形状:
```
Status SetStartCap(LineCap startCap); // 设置起点的线帽
Status SetEndCap(LineCap endCap); // 设置终点的线帽
// 设置起点、终点和虚线的线帽
Status SetLineCap(LineCap startCap, LineCap endCap, DashCap dashCap);
```
其中的线帽枚举 LineCap 为(GdiplusEnums.h):
```
typedef enum {
LineCapFlat = 0, // 平线,直线起点位于平线的中点(默认值)
LineCapSquare = 1, // 方形,高度=线宽,直线起点位于正方形中心
LineCapRound = 2, // 圆形,直径=线宽,直线起点位于圆心
LineCapTriangle = 3, // 三角,高度=线宽,直线起点位于其底边中点
LineCapNoAnchor = 0x10, // 无锚,同平线
LineCapSquareAnchor = 0x11, // 方形锚,高度>线宽,直线起点位于正方形中心 LineCapRoundAnchor = 0x12, // 圆形锚,直径>线宽,直线起点位于圆心 LineCapDiamondAnchor = 0x13, // 菱形锚,高度>线宽,直线起点位于菱形中心 LineCapArrowAnchor = 0x14, // 箭头锚,高度>线宽,直线起点位于箭头的尖点 LineCapCustom = 0xff // 自定义线帽
} LineCap;
```
自定义线帽,需要用到 GDI+专门为此定义的类 CustomLineCap。其构造函数为:
```
CustomLineCap(const GraphicsPath *fillPath, const GraphicsPath *strokePath, LineCap baseCap, REAL baseInset);
```
其中要用到图形路径类 GraphicsPath,该类中有图形各种添加图形方法,只是把 Graphics 类
绘图方法名中的 Draw 改成 Add 即可。例如:AddLine、AddRectangle 和 AddPolygon 等。使 用时,可以先创建一个空路径,然后调用这些添加图形方法若干次,就可以生成路径了。
![image](https://box.kancloud.cn/2016-04-18_57144a7f36e43.jpg)
例如(各类线帽,参见图 14-23 和图 14-24):
![image](https://box.kancloud.cn/2016-04-18_57144a7f484f5.jpg)
箭头线帽 构造箭头线帽头尾所使用的坐标系
图 14-23 自定义线帽
```
// 自定义箭头线帽
GraphicsPath startPath, endPath; // 创建起点和终点路径对象
startPath.AddRectangle(Rect(-10, -5, 20, 10)); // 起点矩形
Point polygonPoints[4] = {Point(0, -20), Point(10, 0),
Point(0, -10), Point(-10, 0)};
endPath.AddPolygon(polygonPoints, 4); // 终点箭头
CustomLineCap startCap(NULL, &startPath); // 创建起点线帽
CustomLineCap endCap(NULL, &endPath); // 创建终点线帽
// 定义笔
Pen pen(Color::Black, 20); // 画带线帽粗线的黑笔
Pen redPen(Color::Red); // 画不带线帽细线的红笔
// 中英文线帽字符串数组
CString cstrs[] = {L"平线帽", L"方线帽", L"圆线帽", L"三角线帽",
L"无锚线帽", L"方锚线帽", L"圆锚线帽", L"菱锚线帽", L"箭锚线帽", L"定制线帽"};
CString estrs[] = {L"LineCapFlat", L"LineCapSquare",
L"LineCapRound", L"LineCapTriangle", L"LineCapNoAnchor", L"LineCapSquareAnchor",
L"LineCapRoundAnchor", L"LineCapDiamondAnchor", L"LineCapArrowAnchor",
L"LineCapCustom"};
// 创建字体
FontFamily fontFamily(L"Times New Roman"); // 对应中文的"宋体"
Font font(&fontFamily, 10.5); // 五号字
// 绘制各种线帽
Graphics graph(pDC->m_hDC);
for (int i = 0; i <= 9; i++) {
// 画线循环
LineCap lc = (LineCap)(i < 4 ? i : i + 12); // 线帽常量(整数)
if(i < 9)
pen.SetLineCap(lc, lc, DashCapFlat); // 标准线帽
else
{
// 自定义线帽(i = 9)
pen.SetCustomStartCap(&startCap); // 设置自定义的起点线帽
pen.SetCustomEndCap(&endCap); // 设置自定义的终点线帽
pen.SetWidth(3.0f); // 重新设置线宽为 3 个像素
}
int y = 20 + i * 40; // 计算直线的垂直坐标
graph.DrawLine(&pen, 100, y, 400, y); // 画带线帽的粗线
graph.DrawLine(&redPen, 100, y, 400, y); // 画不带线帽的细线
// 绘制中英文线帽字符串
graph.DrawString(cstrs[i], -1, &font,
PointF(15.0f, y - 8.0f), &brush);
graph.DrawString(estrs[i], -1, &font,
PointF(425.0f, y - 8.0f), &brush);
}
```
![image](https://box.kancloud.cn/2016-04-18_57144a7f59b36.png)
图 14-24 线帽的种类 图 14-25 箭头线帽的旋转直线簇 又例如(旋转箭头线帽,参见图 14-25):
```
// startCap 和 endCap 的创建同上例,需包含头文件 <math.h>
Pen pen(Color::DarkGreen, 2);
pen.SetCustomStartCap(&startCap);
pen.SetCustomEndCap(&endCap);
double radian = 3.14159265358979323846 / 180.0;
for (int i = 0; i < 360; i += 10)
graph.DrawLine(&pen, 220, 220, 220 + (INT) (200 *
cos(i * radian)), 220 + (INT) (200 * sin(i * radian)));
```
方法 SetLineCap 的最后一个输入参数 DashCap dashCap,用于设置虚线内部各线段端点 的形状。其取值是枚举类型(GdiplusEnums.h):
```
typedef enum {
DashCapFlat = 0, // 平线(默认值)
DashCapRound = 2, // 圆形
DashCapTriangle = 3 // 三角
} DashCap;
```
可见,只有三种选择:平、圆和三角。之所以枚举常量所对应的值不连续,是因为要同 LineCap 枚举的对应常量一致。
注意,虚线帽的设置,只影响其虚线内部的线段,不会影响整条虚线的头尾形状,它们 是由 SetLineCap 方法的前两个参数来分别设置的。例如(参见图 14-26):
```
Graphics graph(pDC->m_hDC);
Pen pen(Color::Black, 10);
pen.SetLineCap(LineCapFlat, LineCapFlat, **DashCapFlat**);
//pen.SetLineCap(LineCapFlat, LineCapFlat, **DashCapRound**);
//pen.SetLineCap(LineCapFlat, LineCapFlat, **DashCapTriangle**);
REAL dashVals[4] = {5.0f, 2.0f, 15.0f, 4.0f};
for (int i = 0; i <= 5; i++)
{
pen.SetDashStyle((DashStyle)i);
if (i == 5)
pen.SetDashPattern(dashVals, 4);
graph.DrawLine(&pen, 10, 10 + i * 20, 400, 10 + i * 20);
}
```
![image](https://box.kancloud.cn/2016-04-18_57144a7f74de2.jpg)
DashCapRound 圆虚线帽
![image](https://box.kancloud.cn/2016-04-18_57144a862cedc.jpg)
DashCapFlat 平虚线帽
![image](https://box.kancloud.cn/2016-04-18_57144a863fdbe.jpg)
DashCapTriangle 三角虚线帽
图 14-26 虚线帽
(5)线连接
笔的线连接(join)属性,也是 GDI+新增的功能。可以使用 Pen 类的方法:
```
Status SetLineJoin( LineJoin lineJoin);
```
来设置笔的线连接属性。其中输入参数为枚举类型 LineJoin:
```
enum LineJoin {
LineJoinMiter = 0, // 斜接(默认值)
LineJoinBevel = 1, // 斜截
LineJoinRound = 2, // 圆角
LineJoinMiterClipped = 3 // 斜剪
};
```
例如(参见图 14-27):
```
Graphics graph(pDC->m_hDC);
Pen pen(Color::DarkGreen, 40);
for (int i = 0; i < 4; i++)
{
pen.SetLineJoin((LineJoin)i);
graph.DrawRectangle(&pen, 40 + i * 150, 40, 100, 100);
}
```
![image](https://box.kancloud.cn/2016-04-18_57144a8652641.jpg)
LineJoinMiter LineJoinBevel LineJoinRound LineJoinMiterClipped
斜接 斜截 圆角 斜剪
图 14-27 线连接
从该例还看不出斜剪与斜接有什么区别,因为斜剪 LineJoinMiterClipped 主要针对交角 很小,相交部分很长的情形。在斜剪线连接方式下,可以调用 Pen 类的方法
```
Status SetMiterLimit(REAL miterLimit);
```
来设置相交部分的最大限制长度,默认是 10.0(相对于线宽的比值)。
对 LineJoinMiterClipped 方式的线连接,如果 miterLimit < 相交部分的长度,则会截断 至线头(同斜截方式,相当于 miterLimit = 1.0);如果 miterLimit >= 相交部分的长度,则绘 制完整的相交部分。
但是对 LineJoinMiter 方式的线连接,如果 miterLimit < 相交部分的长度,则会截断至
miterLimit 所指定比例的长度;如果 miterLimit >= 相交部分的长度,则绘制完整的相交部分。 例如(参见图 14-28):
```
Graphics graph(pDC->m_hDC);
Pen redPen(Color::Red); // 画细线的红笔
Pen pen(Color::DarkGreen, 40.0f); // 画粗线的绿色笔
Point points[] = {Point(20, 100), Point(400, 130),
Point(20, 160)}; // 点数组
pen.SetLineJoin(LineJoinMiter); // 斜接
//pen.SetLineJoin(LineJoinBevel); // 斜截
//pen.SetLineJoin(LineJoinRound); // 圆角
//pen.SetLineJoin(LineJoinMiterClipped); // 斜剪
//pen.SetMiterLimit(20.0f); // 设置斜接限长
graph.DrawLines(&pen, points, 3); // 画粗线
graph.DrawLines(&redPen, points, 3); // 画细线
```
![image](https://box.kancloud.cn/2016-04-18_57144a8664e83.png)
图 14-28 小交角线连接 图 14-29 不同斜接限长下的斜接线连接 如果不断修改斜接线连接 LineJoinMiter 方式下的线长限制(0.0f~13.0f),则可得到不同
截断长度的斜交角。例如(参见图 14-29):
```
pen.SetLineJoin(LineJoinMiter); // 斜接
pen.SetMiterLimit(1.0f/*~13.0f*/); // 设置斜接限长
```
### 1.7.2 刷
与 GDI 中的一样,GDI+中的刷(brush)也是画填充图的工具,GDI+中也有与 GDI 相 对应的实心刷(单色刷)、条纹刷(影线刷)和纹理刷(图像刷)。不过,GDI+又新增加了 功能强大的线性渐变刷和路径渐变刷,而且还为所有这些刷各自建立了对应的类,基类是Brush(功能少)。
![image](img/Image_046.png)
图 14-30 是 GDI+中各种刷类的层次结构图, 所有刷类都被定义在头文件 Gdiplus Brush.h 中。
(1)刷基类 Brush
![](https://box.kancloud.cn/2016-04-18_57144a86872e1.png)
Brush 是所有 GDI+具体刷类的基类,Brush 类没有自己的公用构造函数,属于非实例化
类(用户不能创建 Brush 类的对象和实例),只是定义了三个公用的方法(接口):
```
Brush *Clone( VOID) const; // 克隆,用于复制 Brush 及其派生类对象 Status
GetLastStatus(VOID); // 获取最后状态,返回刷对象最近的错误状态
BrushType GetType(VOID); // 获取类型,返回当前(派生)刷的类型枚举常量
```
下面是 BrushType 枚举类型的定义(GdiplusEnums.h):
```
typedef enum {
BrushTypeSolidColor = 0, // 实心单色刷
BrushTypeHatchFill = 1, // 影线条纹填充刷
BrushTypeTextureFill = 2, // 图像纹理填充刷
BrushTypePathGradient = 3, // 路径渐变刷
BrushTypeLinearGradient = 4 // 线性渐变刷
} BrushType;
```
(2)实心刷类 SolidBrush
GDI+中,实心的单色刷对应于 SolidBrush 类,它只有一个构造函数:
```
SolidBrush(const Color &color);
```
输入参数为颜色对象的引用。
在前面的例子中已经多次使用了 SolidBrush 类,下面再举一个画正叶曲线的例子,下面 是正叶曲线的极坐标方程及其到直角坐标系的转换公式:
![image](https://box.kancloud.cn/2016-04-18_57144a869d816.png)
其中,l 为叶片长度、n 为叶片数目。
因为 GDI+并没有画正叶曲线的专门函数,所以需要用多边形、样条曲线或图形路径来 刻画它。可以使用填充多边形、填充封闭曲线和填充图形路径等方式来进行绘制,下面的代码使用的是填充闭基样条曲线,输出结果如图 14-31 所示。
+ 绘制单个正叶曲线的函数代码:
```
#include <math.h>
void DrawLeaves(Graphics &graph, const Color col, Point &O,
int l, int n) {
double radian = 3.14159265358979323846 / 180.0;
int m = n < 5 ? 21 : 11;
int N = m * n;
double da = 360.0 / N; PointF *ps = new PointF[N];
for (int i = 0; i < N; i++) {
double r = abs(l * cos(radian * (n * i * da)/ 2.0)),
x = r * cos(i * da * radian),
y = r * sin(i * da * radian);
ps[i].X = REAL(O.X + x);
ps[i].Y = REAL(O.Y + y);
}
graph.FillClosedCurve(&SolidBrush(col), ps, N);
}
```
+ 绘制系列彩色正叶曲线的调用序列:
```
Graphics graph(pDC->m_hDC);
Color cols[] = {Color::Aqua, Color::Aquamarine, Color::DarkBlue, Color::DarkKhaki,
Color::DeepPink, Color::BlueViolet, Color::Brown, Color::BurlyWood, Color::CadetBlue,
Color::Chartreuse, Color::Turquoise, Color::Coral, Color::CornflowerBlue,
Color::Crimson, Color::DarkCyan};
bool color = true; // false;
for (int i = 0; i < 15; i++)
DrawLeaves(graph, color ? cols[i] : Color::Green,
Point(100 + 200* (i % 5), 100 + 200 * (i / 5)), 100, i + 1);
```
![image](https://box.kancloud.cn/2016-04-18_57144a86af13d.jpg)
图 14-31 彩色正叶曲线系列
(3)条纹刷类 HatchBrush
条纹是一种重复填充的小方形图案,一般为横线、竖线、斜线和小方块等构成。GDI+ 中,条纹刷(hatch brush 影线刷/阴影刷)对应于 HatchBrush 类,它也只有一个构造函数:
```
HatchBrush(HatchStyle hatchStyle, const Color &foreColor, const Color &backColor = Color());
```
其中:第一个参数为条纹类型,第二个参数为前景色(条纹色),第三个参数为背景色(空隙色)。
GDI+中一共有 53 种条纹风格,而 GDI 中只有前 6 种。条纹风格枚举 HatchStyle 也被定 义在头文件 GdiplusEnums.h 中:
```
enum HatchStyle {
HatchStyleHor izontal, // 0:横线
HatchStyleVertical, // 1:竖线
HatchStyleForwardDiagonal, // 2:正斜线
HatchStyleBackwardDiagonal, // 3:反斜线
HatchStyleCross, // 4:十字线
HatchStyleDiagonalCross, // 5:斜十字线
HatchStyle05Percent, // 6:5%
HatchStyle10Percent, // 7:10%
...
HatchStyleSphere, // 47:球面
HatchStyleSmallGrid, // 48:小网格
HatchStyleSmallChecker Board, // 49:小跳棋盘
HatchStyleLargeCheckerBoard, // 50:大跳棋盘
HatchStyleOutlinedDiamond, // 51:斜纲线
HatchStyleSolidDiamond, // 52:实菱形 HatchStyleTotal, // = 53(0 ~ 52):条纹风格总数 HatchStyleLargeGrid = HatchStyleCross, // 4:大网格
HatchStyleMin = HatchStyleHorizontal, // 0:条纹风格最小值 HatchStyleMax = HatchStyleTotal - 1, // 52:条纹风格最大值
};
```
例如(参见图 14-32):
```
Graphics graph(pDC->m_hDC); Pen pen(Color::Black);
SolidBrush textBrush(Color::Red);
FontFamily fontFamily(L"Times New Roman");
Font font(&fontFamily, 18);
CString str;
StringFormat sfmt; // 文本格式
sfmt.SetAlignment(StringAlignmentCenter); // 水平对齐
sfmt.SetLineAlignment(StringAlignmentCenter); // 垂直对齐
int w = 50, h = 50, s = 5;
for (int i = 0; i < 53; i++) { // 主循环
HatchBrush brush(HatchStyle(i), Color::Black, Color::White);
RectF rect(REAL(s + (i % 10) * (w + s)),
REAL(s + (i / 10) * (h + s)), REAL(w), REAL(h));
graph.FillRectangle(&brush, rect); // 画条纹块
str.Format(L"%d", i); // 绘制数字编号的文本串:
graph.DrawString(str, str.GetLength(), &font, rect,
&sfmt, &textBrush);
}
```
![image](https://box.kancloud.cn/2016-04-18_57144a86ca8cc.jpg)
图 14-32 条纹刷的条纹风格
与 GDI 一样,在 GDI+中也可以调整条纹刷和图像刷的起点。这需要使用图像类 Graphics 的方法 SetRenderingOrigin 来设置渲染原点为(x, y)(默认为(0, 0)):
```
Status SetRenderingOrigin(INT x, INT y);
```
(4)纹理刷类 TextureBrush
纹理刷(texture brush)就是图像刷,它将刷中所装入的图像,在目标区域中进行平铺, 可达到纹理效果。GDI 中也有图像刷,但仅限于使用位图资源和(非常费事才能使用)BMP 文件。在 GDI+中,纹理刷所对应的是 TextureBrush 类,它有 7 个构造函数,最常用的为:
```
TextureBrush(Image* image, WrapMode wrapMode = WrapModeTile);
```
其中,第一个参数是图像对象的指针,第二个参数是排列方式的枚举常量(GdiplusEnums.h):
```
typedef enum {
WrapModeTile = 0, // 平铺(瓦)(默认值)
WrapModeTileFlipX = 1, // 平铺且 X 向翻转(相邻列左右翻转)
WrapModeTileFlipY = 2, // 平铺且 Y 向翻转(相邻行上下翻转)
WrapModeTileFlipXY = 3, // 平铺且 XY 向翻转(相邻行列左右上下翻转)
WrapModeClamp = 4 // 不平铺(不重复,夹住)
} WrapMode;
```
还可以用纹理刷类的下面两个方法来设置和获取刷的排列方式:
```
Status SetWrapMode(WrapMode wrapMode); WrapMode GetWrapMode() const;
```
例如(参见图 14-33):
```
Graphics graph(pDC->m_hDC);
Image img(L"张东健.bmp");
TextureBrush brush(&img, WrapModeTile/*FlipXY*/);
//TextureBrush brush(&img, **WrapModeClamp**);
RECT rect;
GetClientRect(&rect);
graph.FillRectangle(&brush, RectF(0.0f, 0.0f, REAL(rect.right), REAL(rect.bottom)));
```
![image](https://box.kancloud.cn/2016-04-18_57144a86e3cd0.jpg)
平铺(WrapModeTile)
![image](https://box.kancloud.cn/2016-04-18_57144a87026c9.jpg)
平铺且 X 向翻转(WrapModeTileFlipX)
![image](https://box.kancloud.cn/2016-04-18_57144a871549b.jpg)
平铺且 Y 向翻转(WrapModeTileFlipY)
![image](https://box.kancloud.cn/2016-04-18_57144a872bafc.jpg)
平铺且 XY 向翻转(WrapModeTileFlipXY)
![image](https://box.kancloud.cn/2016-04-18_57144a8743fc4.jpg)
不平铺(WrapModeClamp)
图 14-33 纹理刷排列方式
纹理刷类 TextureBrush 中,还有几个方法,可以对刷中的图像进行平移(translate)、旋 转(rotate)和缩放(scale)等变换(transform)(这是 GDI 里所没有的功能):
```
Status TranslateTransform(REAL dx, REAL dy, MatrixOrder order = MatrixOrderPrepend);
Status RotateTransform(REAL angle, MatrixOrder order = MatrixOrderPrepend) ;
Status ScaleTransform(REAL sx, REAL sy, MatrixOrder order = MatrixOrderPrepend);
```
例如(参见图 14-34):
```
Graphics graph(pDC->m_hDC); Image img(L"张东健.bmp");
TextureBrush brush(&img);
//brush.TranslateTransform(30, 30); // 平移(30, 30)
brush.RotateTransform(30); // 旋转 30 度
//brush.ScaleTransform(3, 1); // 水平放大 3 倍
//brush.ScaleTransform(1, 3); // 垂直放大 3 倍
RECT rect;
GetClientRect(&rect);
graph.FillRectangle(&brush, RectF(0.0f, 0.0f, REAL(rect.right), REAL(rect.bottom)));
```
![image](https://box.kancloud.cn/2016-04-18_57144a875921f.jpg)
平移(30, 30)
![image](https://box.kancloud.cn/2016-04-18_57144a876daf7.jpg)
旋转 30 度
![image](https://box.kancloud.cn/2016-04-18_57144a87814c3.jpg)
水平放大 3 倍
![image](https://box.kancloud.cn/2016-04-18_57144a8795582.jpg)
垂直放大 3 倍
图 14-34 纹理刷变换
(5)线性渐变刷类 LinearGradientBrush
线性渐变刷(linear gradient brush 线性梯度刷)使用逐渐变化的颜色填充目标区域。是 GDI+新增的功能。线性渐变刷所对应的类为 LinearGradientBrush,它有 6 个构造函数,前 3 个是整数版,后 3 个是对应的浮点数版。下面是 3 个整数版的构造函数:
```
LinearGradientBrush(const Point& point1, const Point& point2, const Color& color1, const Color& color2);
LinearGradientBrush(const Rect& rec t, const Color& color1, const Color& color2, LinearGradientMode mode);
LinearGradientBrush(const Rect& rect, const Color& color1, const Color& color2, REAL angle, BOOL is AngleScalable = FALSE);
```
在这三种构造函数中,第一个是点到点、第二个是矩形与渐变模式、第三个是是矩形与旋转角度。限于篇幅,这里只介绍其中点到点的整数版构造函数的具体使用方法。
1)点到点渐变
点到点的渐变是指刷子所填充的颜色,沿着点 point1 到点 point2 的直线,从颜色 color1 连续变化到 color2。若 p1 和 p2 点的 y 值相等,则为水平方向的渐变;若 p1 和 p2 点的 x 值 相等,则为垂直方向的渐变;p1 和 p2 点的 x 和 y 值都不相等,则为斜对角方向的渐变。
例如(参见图 14-35):
```
Graphics graph(pDC->m_hDC);
Point p1(10, 10), p2(110, 10), p3(10, 110), p4(230, 10), p5(330, 110);
Size size(100, 100);
Color col1(255, 0, 0), col2(0, 0, 255);
LinearGradientBrush hbrush(p1, p2, col1, col2);
graph.FillRectangle(&hbrush, Rect(p1, size));
LinearGradientBrush vbrush(p1, p3, col1, col2);
graph.FillRectangle(&vbrush, Rect(Point(120, 10), size));
LinearGradientBrush dbrush(p4, p5, col1, col2);
graph.FillRectangle(&dbrush, Rect(Point(230, 10), size));
```
![image](https://box.kancloud.cn/2016-04-18_57144a87aa1bf.jpg)
水平渐变 垂直渐变 对角渐变
图 14-35 线性渐变刷
其实,线性渐变刷默认是按 WrapModeTile 平铺方式重复排列的(原点是 point1),例如(参见图 14-36 a)):
```
Graphics graph(pDC->m_hDC);
Point p1(10, 10), p2(110, 10), p3(10, 110);
Color col1(255, 0, 0), col2(0, 0, 255);
LinearGradientBrush hbrush(p1, p2, col1, col2);
//hbrush.SetWrapMode(WrapModeTileFlipX);
graph.FillRectangle(&hbrush, Rect(p1, Size(400, 200)));
LinearGradientBrush vbrush(p1, p3, col1, col2);
//vbrush.SetWrapMode(WrapModeTileFlipX);
graph.FillRectangle(&vbrush, Rect(Point(420, 10), Size(200, 410)));
LinearGradientBrush dbrush(p1, Point(110, 100), col1, col2);
//dbrush.SetWrapMode(WrapModeTileFlipX);
graph.FillRectangle(&dbrush, Rect(Point(10, 220), Size(400, 200)));
```
你也可以将上面代码中的注释符“//”去掉,利用线性渐变刷类的方法
```
Status SetWrapMode(WrapMode wrapMode);
```
来设置画刷的排列方式为 WrapModeTileFlipX 平铺并水平翻转,参见图 14-36 b)。
![image](https://box.kancloud.cn/2016-04-18_57144a87bb582.jpg)
图 14-37 参数的含义
![image](https://box.kancloud.cn/2016-04-18_57144a87cf56d.jpg)
a) 平铺重复排列 b) 加水平翻转
图 14-36 按平铺重复排列的线性渐变刷
下面是一个利用水平线性渐变刷来画阴阳八卦中的阴阳鱼例子(参见图 14-37 和图 14-38):
```
LinearGradientBrush R2BBrush(Point(0, 10), Point(200, 10), Color(255, 0, 0), Color(0, 0, 255));
LinearGradientBrush B2YBrush(Point(0, 10), Point(200, 10),
Color(0, 0, 255), Color(255, 255, 0));
Pen bluePen(Color(255, 0, 0, 255));
Rect circleRect(0, 0, 200, 200);
Rect leftRect(0, 50, 100, 100);
Rect rightRect(100, 50, 100, 100); Graphics graph(pDC->m_hDC);
graph.FillPie(&R2BBrush, circleRect, 0.0f, 180.0f);
graph.FillPie(&B2YBrush, circleRect, 180.0f, 180.0f);
graph.FillPie(&R2BBrush, leftRect, 180.0f, 180.0f);
graph.FillPie(&B2YBrush, rightRect, 0.0f, 180.0f); int r = 10;
graph.FillEllipse(&SolidBrush(Color(0, 255, 0)), 50 - r, 100 - r, 2 * r, 2 * r);
graph.FillEllipse(&SolidBrush(Color(255, 0, 255)), 150 - r, 100 - r, 2 * r, 2 * r);
```
![image](https://box.kancloud.cn/2016-04-18_57144a87e145b.jpg)
![image](https://box.kancloud.cn/2016-04-18_57144a87f367d.jpg)
![image](https://box.kancloud.cn/2016-04-18_57144a881028e.png)
![image](https://box.kancloud.cn/2016-04-18_57144a882e374.jpg)
图 14-38 绘制阴阳鱼的分步输出结果
2)多色渐变 线性渐变刷还有很多其他功能,例如可利用刷的方法:
```
Status SetInterpolationColors(const Color *presetColors, const REAL *blendPositions, INT count);
```
来设置多色渐变。其中,presetColors 为多色数组、blendPositions 为以百分比表示的对应混色点的位置(首、尾值必须为 0.0f 和 1.0f,中间的值应该按递增序排列)、count 为颜色和混 色点位的数目。例如(参见图 14-39):
```
Color cols[] = {Color::Red, Color::Orange, Color::Yellow, Color::Green, Color::Cyan, Color::Blue, Color::Purple, Color::Magenta};
REAL bps[] = {0.0f, 0.15f, 0.3f, 0.45f, 0.6f, 0.75f, 0.875f, 1.0f};
LinearGradientBrush brush(Point(10, 10), Point(810, 10), Color::Black, Color::White);
brush.SetInterpolationColors(cols, bps, 8);
graph.FillRectangle(&brush, Rect(10, 10, 800, 100));
```
![image](https://box.kancloud.cn/2016-04-18_57144a88405e8.jpg)
图 14-39 多色渐变 另外,也可以像纹理刷和条纹刷一样,设置线性渐变刷的渲染原点等。 路径渐变刷的内容,安排到下一章的第 15.1.2 小节中,在介绍过路径的基本概念和使用方法之后再来讲解。