ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
## 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)的单色笔。 如果颜色的α 值&lt;255,则所创建的笔就是带透明度的笔。 (1)笔对齐 当笔宽大于 1 时,默认情况下,是以笔的中心与绘图坐标对齐。但是,也可以采用 Pen类的方法: ``` Status SetAlignment(PenAlignment penAlignment); ``` 设置为内对齐,其输入参数取枚举类型 PenAlignment 的符号常量: ``` typedef enum { PenAlignmentCenter = 0, // 中心对齐(默认值) PenAlignmentInset = 1 // 内对齐 } PenAlignment; ``` 例如(输出结果如图 14-19 所示): ``` Graphics graph(pDC-&gt;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()-&gt;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 中的非实线线型,对宽度&gt;1 的笔无效;而 GDI+的笔对任意非零宽度的笔都是有效的。 + GDI+中新增了一种风格——自定义虚线风格。 具体的自定义虚线风格,由 Pen 类的设置虚线图案的方法 ``` Status SetDashPattern(const REAL *dashArray, INT count); ``` 来设置,其中的实数数组 dashArray 含若干个正实数(单位为像素),按线、空、线、空、„„ 的交叉方式排列;参数 count 为数组中实数的个数(须&gt;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 &lt;= 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, // 方形锚,高度&gt;线宽,直线起点位于正方形中心 LineCapRoundAnchor = 0x12, // 圆形锚,直径&gt;线宽,直线起点位于圆心 LineCapDiamondAnchor = 0x13, // 菱形锚,高度&gt;线宽,直线起点位于菱形中心 LineCapArrowAnchor = 0x14, // 箭头锚,高度&gt;线宽,直线起点位于箭头的尖点 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-&gt;m_hDC); for (int i = 0; i &lt;= 9; i++) { // 画线循环 LineCap lc = (LineCap)(i &lt; 4 ? i : i + 12); // 线帽常量(整数) if(i &lt; 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 的创建同上例,需包含头文件 &lt;math.h&gt; Pen pen(Color::DarkGreen, 2); pen.SetCustomStartCap(&startCap); pen.SetCustomEndCap(&endCap); double radian = 3.14159265358979323846 / 180.0; for (int i = 0; i &lt; 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-&gt;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 &lt;= 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-&gt;m_hDC); Pen pen(Color::DarkGreen, 40); for (int i = 0; i &lt; 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 &lt; 相交部分的长度,则会截断 至线头(同斜截方式,相当于 miterLimit = 1.0);如果 miterLimit &gt;= 相交部分的长度,则绘 制完整的相交部分。 但是对 LineJoinMiter 方式的线连接,如果 miterLimit &lt; 相交部分的长度,则会截断至 miterLimit 所指定比例的长度;如果 miterLimit &gt;= 相交部分的长度,则绘制完整的相交部分。 例如(参见图 14-28): ``` Graphics graph(pDC-&gt;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 &lt; 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-&gt;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 &lt; 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-&gt;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 &lt; 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-&gt;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-&gt;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-&gt;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-&gt;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 小节中,在介绍过路径的基本概念和使用方法之后再来讲解。