GDI+绘图轻松入门[6]-Graphicspath和它的兄弟姐妹Figure、Maker、Region

—前面一片博文,我们详细介绍了绘图坐标的内容《绘图坐标的理解和应用》,但我们在绘图时都只是在用一个单一的绘图对象Graphics,如果我们想对图形来一些局部的操作,比如移动,旋转,变形等,那最终会造成整个图像跟着变化。
很明显,这不是我们的初中,所以,这里我们介绍的内容将告诉我们,如何将绘制的内容分成更小的“团队”来管理。这些小的团队分别是Graphicspath、region、figure,可以说给他们将给我们的绘图提供了更广阔的空间。


文章原出处: https://haigear.blog.csdn.net/article/details/129020502

一、Graphics

首先,我们还是回顾一下Graphics,我们前面的绘图基本上都是基于这个对象的,我们常用的都是Graphics提供的方法,他们主要分为两类,即绘制轮廓类draw和填充类fill,如下表(我按照使用难易和频率排序):

函数名称 函数说明
DrawLine 画线
DrawString 绘制文字
DrawRectangle 画矩形
DrawArc 画弧
DrawEllipse 画椭圆
DrawImage 画图像
DrawPolygon 画多边形
DrawPath 通过路径画线和曲线
DrawPie 画饼形
DrawBezier 画立体的贝尔塞曲线
DrawBeziers 画连续立体的贝尔塞曲线
DrawClosedCurve 画闭合曲线
DrawCurve 画曲线
FillEllipse 填充椭圆
FillPath 填充路径,注意:路径如果没有封闭时默认将起点和终点连接起来填充
FillPie 填充饼图
FillPolygon 填充多边形
FillRectangle 填充矩形
FillRectangles 填充矩形组
FillRegion 填充区域,区域自身就带有填充的方法

那么这里面有两个很特别的对象,他们就是Path和Region,尤其在填充部分我们可以看到FillPath和FillRegion两个方法。下面我们来介绍他们。

二、Graphicspath

首先,你也许会有一个疑问,有了graphic对象后,为什么还要一个graphicpath呢?path能够起到什么作用呢?
我们仔细看graphic的两类方法可以发现,除了path和region外都是绘制的比较规则的图形,我们要绘制不规则图形那就无计可施了,所以,graphicpath的作用就是绘制不规则图形。

1、构造函数和属性

(1) 属性

属性 说明
FillMode 如果填充路径的内部区域,这个属性表示填充的方式。其值取自于FiIIMode枚举(它有两个值Alternate和 Winding)。可以获取和设置这个属性的值,参见下面的说明
PathData 它会获取一个PathData对象,该对象包含了如何构建路径的信息(PathData对象有两个属性Points和Types,它们给出了与这个表中PathPoints和PathTypes属性所描述的相同的数组)
PathPoints 以数组的形式给出路径上的点
PathTypes GraphicsPath中的每个点都有一个类型(例如Start、Line、Bezier、CloseSubpath)。这个属性获取路径中点的类型,其形式是一个数组。注意所有可能的点类型都列在PathPointType枚举中,该枚举system.Drawing.Drawing2D枚举的一部分
PointCount 它获取由PathPoints 或PathTypes属性返回的数组中的元素个数

(2)构造函数

函数重载方式 说明
GraphicsPath() 用 GraphicsPath 的 FillMode 值初始化 Alternate 类的新实例。
GraphicsPath(FillMode) 用指定的 GraphicsPath 枚举初始化 FillMode 类的新实例。
GraphicsPath(Point[], Byte[]) 使用指定的 GraphicsPath 和 PathPointType 数组初始化 Point 类的新实例。
GraphicsPath(Point[], Byte[], FillMode) 使用指定的 GraphicsPath 和 PathPointType 数组以及指定的 Point 枚举元素初始化 FillMode 类的新实例。
GraphicsPath(PointF[], Byte[]) 使用指定的 GraphicsPath 和 PathPointType 数组初始化 PointF 数组的新实例。
GraphicsPath(PointF[], Byte[], FillMode) 使用指定的 GraphicsPath 和 PathPointType 数组以及指定的 PointF 枚举元素初始化 FillMode 数组的新实例。

(3)方法

至于graphicpath的方法,数量太多,这里就不一一列出了,基本和graphic对象的类似,只不过graphic是最终的绘制者,而path这里只是数据准备着,所以方法都以add开头,addline,addlines,addArc,addRectangle等等,但有两个方法我们必须提出来,后面我们将单独介绍他们。
这里貌似冒出来了一个新概念figure,实际它是配合graphicpath来使用的,这里给出它的几个方法是使用说明。

方法名 说明
StartFigure() 不闭合当前图形即开始一个新图形。 后面添加到该路径的所有点都被添加到此新图形中。
CloseAllFigures() 闭合此路径中所有开放的图形并开始一个新图形。 它通过连接一条从图形的终结点到起始点的直线,闭合每一开放的图形。
CloseFigure() 闭合当前图形并开始新的图形。 如果当前图形包含一系列相互连接的直线和曲线,该方法通过连接一条从终结点到起始点的直线,闭合该环回。

有一个明显的特征这里要指出,所有的方法中没有一个是Fill或者draw开头,这说明它就是一个地地道道的“图形数据准备者“”,要详细了解可以查看官网GraphicsPath

以上了解了构造函数、属性、方法,那这个类我们也了解得差不多了,反正它就是为绘制不规则的图像轮廓而生。既然这样,我们就来绘制一个不规则图形吧!

2、GraphicPath的创建

可以说GraphicsPath就是一组图形序列,创建GraphicPath自然是使用其构造函数,因为目前没有说有一个功能可以将一个图形反过来编程图形序列(可能是缺少拆分图形序列的标准)。

(1)、构造函数

既然是构造函数,那我们自然可以进到GraphicsPath的类中去看看,目前支持6中构造函数重载:

扫描二维码关注公众号,回复: 15488188 查看本文章
 public GraphicsPath();
 public GraphicsPath(FillMode fillMode);
 public GraphicsPath(PointF[] pts, byte[] types);
 public GraphicsPath(Point[] pts, byte[] types);
 public GraphicsPath(PointF[] pts, byte[] types, FillMode fillMode);
 public GraphicsPath(Point[] pts, byte[] types, FillMode fillMode);

获取graphicpath的放其实还有fromRectangle

(2)、Addxxx()系列方法

很显然,既然GraphicsPath是图形序列,那这个序列的构成就如前面我们学习过的数组、列表、词典、哈希表一样,必须一个个将图形元素添加进去(我们可以理解为GraphicsPath是图形绘制中的专用的数据结构),这里我们就不一一介绍这一系列的添加图形元素的函数了。

(3)、 getBound

我们都知道,图形绘制中几乎从来都离不开矩形Rectangle,所以这里也提供了一个返回矩形的方法。

 public RectangleF GetBounds(Matrix? matrix);
 public RectangleF GetBounds();
 public RectangleF GetBounds(Matrix? matrix, Pen? pen);

当然,我们也直接可以将一个控件的bounds属性赋值给一个声明好的Rectangle。

(4)、变形

对路径来说,需要改变位置、方向、大小、已经其中的文字的大小,色彩,透明度都需要依靠我们的变形。

public void Transform(Matrix matrix);

从Transfrom函数的参数看,我们知道,要使用它首先必须创建一个Matrix
这举例说明:

GraphicsPath myPath = new GraphicsPath();
myPath.AddEllipse(0, 0, 100, 200);
Matrix translateMatrix = new Matrix();  //新建一个矩阵
translateMatrix.Translate(100, 0);		//对矩阵使用translate位置变换()
myPath.Transform(translateMatrix);		//最后调用GraphicPath的Tansform方法(以矩阵为参数)实现位置变换

3、 figure

figure我们没有单独使用的时候,都是为graphicpath服务的。一个graphicpath可以隐式的进行一个figure,但可以必须显示的结束一个figure,使用closefigure。

(1)startFigure方法

一般我们在开启一个新的开放图形的时候就使用它,但如果一个graphicpath刚刚创建或我们开启一个新封闭图形完全没有必要使用它,默认系统就会调用它,官方给出的说法叫做隐式开启一个图形。当然我们调用这个函数那就叫做显式开启一个新path图形。

        public void StartFigure();
```### (2)closeFigure方法
我们要结束上一个图形,我们必须调用它,如果不调用它,上一个图形和下一个你想要与前者独立的图形就会被链接在一起,中间就会有一根线。如下图,我们绘制两根线,下面的代码不会有线连接,因为我们使用了closeFigure(也叫作显式关闭一个图形):

```csharp
Graphics g = e.Graphics;
GraphicsPath gp = new GraphicsPath();
gp.StartFigure();
gp.AddLine(300,300, 400, 300);
gp.CloseFigure();
gp.AddLine(300, 320, 400, 320);
g.DrawPath(Pens.Black, gp);

在这里插入图片描述
下面我们不使用Closefigure或者把Closefigure放在第二根线后,那么两根线之间就会出现连接线
在这里插入图片描述
对于封闭图形则不会出现连接线,有不有figure都无所谓了,我们只能看到开放图形之间如果不使用closefigure会有连接线(系统不知道你的图形是否结束),代码如下:

 Graphics g = e.Graphics;
 GraphicsPath gp = new GraphicsPath();
 gp.StartFigure();
 gp.AddEllipse(200, 100, 100, 100);
 gp.AddRectangle(new Rectangle(300, 200, 100, 50));
 gp.AddEllipse(400, 100, 100, 100);
 gp.AddLine(300,300, 400, 300);
 gp.AddLine(300, 320, 400, 320);
 gp.CloseFigure();
 g.DrawPath(Pens.Black, gp);

在这里插入图片描述

4、Marker

(1) setMarkers

标题应给出了官网的链接,这里摘抄它的说明如下:
SetMarkers使用 方法在路径中设置标记。 标记用于创建子路径组。 一个或多个子路径可以介于两个标记之间。
此方法在路径上创建一个标记,该标记可用于分隔路径的各个部分。 然后 NextMarker ,可以使用方法循环访问路径中的标记。

        public void SetMarkers();

(2) NextMarker

public int NextMarker (System.Drawing.Drawing2D.GraphicsPath path);

这个函数是GraphicsPathIterator的方法,不是单独的,所以其作用也就很明显了,他的作用就是获取我们前面用setMarkes在GraphicPath中所设置的分段(或者说设了标记)的图形数据集。
详细情况,可参考官方给出的实例
在这里插入图片描述

三、 region

region类在控件的编写时使用比较频繁,我们查看源码就可以发现,最明显的是每个控件都有一个Region属性。
区域的创建都是依靠Region类的重载,我们来看看它的构造重载函数,他们很显然都有一个显著的特点,那就是参数都提供了一个封闭图形的对象(path,rectangel等,当然,如果是离散的点,或者线的话,只能理解为一些离散的封闭图形了):

public Region(GraphicsPath path);
public Region(RegionData rgnData);
public Region(Rectangle rect);
public Region(RectangleF rect);

1.从路径中创建:上述示例

一般的情形,我们都是通过一个封闭图形来获取一个区域region的,如下代码:

GraphicPath path=new GraphicPath();
//path.AddArc
//path.AddLine
Region rg=new Region(path);

2.从各类图形中创建

除了典型的path可以提供封闭图形,那最明显的能够提供封闭图形的就是rectangle了(我个人觉得椭圆也是不错的选项,当时构造函数中没有它)。

Region reg = new Region(new Rectangle(10, 10, 80, 20));

3.从一个区域中创建另一个区域

从一个区域中创建另一个区域我们需要用到RegionData ,是不是这个RegionData 会使我们想起GraphicPath中的PathData?是的,他们都有共同的特点,那就是他们可以为创建相应的对象提供数据。

Region r1 = new Region(new Rectangle(10, 10, 80, 20));
RegionData r1Data = r1.GetRegionData();//区域数据
Region r2 = new Region(r1Data);  //构造函数不接受区域对象本身,可以接受区域数据数组。

4、region和控件刷新的密切关系

控件的刷新函数中有一个invalidate,这个函数里面的一个参数就是region,这个我们必须注意:

重载 说明
Invalidate(Region, Boolean) 使控件的指定区域无效(将其添加到控件的更新区域,下次绘制操作时将重新绘制更新区域),并向控件发送绘制消息。 还可以使分配给该控件的子控件无效。
Invalidate(Rectangle, Boolean) 使控件的指定区域无效(将其添加到控件的更新区域,下次绘制操作时将重新绘制更新区域),并向控件发送绘制消息。 还可以使分配给该控件的子控件无效。
Invalidate(Region) 使控件的指定区域无效(将其添加到控件的更新区域,下次绘制操作时将重新绘制更新区域),并向控件发送绘制消息。
Invalidate(Boolean) 使控件的特定区域无效并向控件发送绘制消息。 还可以使分配给该控件的子控件无效。

除了上述invalidate的刷新函数外,我们每个控件都有一个region的属性,在这里就可以指定控件绘制的区域,如我们在OnPaint函数中可以做个测试,我们可以绘制一个矩形,:

 protected override void OnPaint(PaintEventArgs pe)
 {
    
    
     base.OnPaint(pe);
     Graphics graphics = pe.Graphics;
     graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
     graphics.FillRectangle(Brushes.Red, new Rectangle(0,0,Width,Height));
     graphics.Dispose();
 }

在控件的构造函数中给Region指定一个路径,则显示的图一个圆形,而不是上面的矩形了。


GraphicsPath gp1 = new GraphicsPath();
gp1.AddEllipse(new Rectangle(0, 0, Width, Height));
Region = new Region(gp1);

效果如下:
在这里插入图片描述
这样,两个控件叠加在一起,很显然区Region域外的像素就不会被显示出来,我们也常常用这种思路来实现叠加控件之间的透明像素不遮挡,也就是是我们常说的控件叠加透明(常规情况下设置控件透明只能透明到父容器,而控件叠加时无法透明过去),当然最常见的异形窗口也是靠这个原理来实现。但region实现透明有个最要命的地方,那就是无法反锯齿,要实现反锯齿恐怕需要对每个像素进行相应的滤波平滑处理了,这是后话放下不说。

4.区域的并集和交集

方法 逻辑 举例 操作
Intersect r1.Intersect(r2) 把r1更新为r1和r2之间的交集(即同时包含在r1和r2中的部分)
Union r1.Union(r2) 把rl更新为r1和r2之间的并集(即包含在r1或r2中的部分,或同时包含在r1和r2中的部分)
Xor 异并 r1.Xor(r2) 把r更新为r和r2之间的异并集(即包含在r1或r2中、但没有同时包含在两者中的部分)
Complement r1.Complement(r2) 更新r1,使之包含位于r2中的部分,但不包含最初位于r1中的部分
Exclude 排除 r1.Exclude(r2) 更新r1,使之不包含任何也位于r2中的部分

这里我们来分别测试一下他们的效果,
在这里插入图片描述
上述图形实例的代码如下:

enum RegionOperation
{
    
    
    Union=0,
    Complement=1,
    Intersect=2,
    Exclude=3,
    Xor=4

}
void DrawRegionOperation(RegionOperation rgnOperation ,Graphics g)
{
    
    
    g = this.CreateGraphics();
    Rectangle rect1 = new Rectangle(100, 100, 120, 120);
    Rectangle rect2 = new Rectangle(70, 70, 120, 120);

    Region rgn1 = new Region(rect1);
    Region rgn2 = new Region(rect2);

    g.DrawRectangle(Pens.Blue, rect1);
    g.DrawRectangle(Pens.Red, rect2);

    switch (rgnOperation)
    {
    
    
        case RegionOperation.Union:
            rgn1.Union(rgn2);
            break;
        case RegionOperation.Complement:
            rgn1.Complement(rgn2);
            break;
        case RegionOperation.Intersect:
            rgn1.Intersect(rgn2);
            break;
        case RegionOperation.Exclude:
            rgn1.Exclude(rgn2);
            break;
        case RegionOperation.Xor:
            rgn1.Xor(rgn2);
            break;
        default:
            break;

    }

    g.FillRegion(Brushes.Tomato, rgn1);

    g.Dispose();
}

四、总结

region着重于图形区域填充,path着重于图形轮廓绘制。但region有个很中要的作用,它可以限定绘图的区域,不在这个绘图区域则不绘制(留空,保持透明),所以这个特性可以用来实现控件与控件叠加时的透明显示,当然,其他的透明显示也可以使用它来实现。后面我们会专门介绍GDI+绘图时透明的实现。有兴趣的童鞋可以继续关注。

文章随时可能更新,请关注原出处:https://haigear.blog.csdn.net/article/details/129020502

猜你喜欢

转载自blog.csdn.net/haigear/article/details/129020502