关于GDI+中GraphicsPath进行合并(Union)截切(Exclude)等编程的探讨(1)

我们知道,在GDI+中,两个图形路径(GraphicsPath)的区域(Region)合并,我们可以采用Region.Union方法进行。但使用它之后,我们再想取得合并后的Region的GraphicsPath却变得不再可能。比如下图1红色部分:


图1  合并GraphicsPath后想要达到的效果

它由两个椭圆共同组成:

            Rectangle rect1 = new Rectangle(100, 100, 178, 178);
            Rectangle rect2 = new Rectangle(230, 100, 178, 178);
            GraphicsPath gp = new GraphicsPath(FillMode.Winding);
            gp.AddEllipse(rect1);
            gp.AddEllipse(rect2);

            Bitmap bmp = new Bitmap(520, 520);
            Graphics g = Graphics.FromImage(bmp);
            g.CompositingQuality = CompositingQuality.HighQuality;
            g.InterpolationMode = InterpolationMode.HighQualityBicubic;
            g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
            g.SmoothingMode = SmoothingMode.HighQuality;


            g.DrawPath(new Pen(Color.Red, 3f), gp);
            picEncode.Image = bmp;

但上面的代码并无法得到图1的结果,而是下图2所示的样式:

图2  使用graphicsPath.Add()方法生成的两条图形路径的轮廊绘制

有没有办法将这两个GraphicsPath真正合并呢?

首先来看看GraphicsPath背后的东东:gp.PathData,它里面包含有路径的点 (points)  和点的类型(types)数组。

对于每一点,byte值0 到 2 都表示点的类型,而 3 到 7 则保存一组可指定点的属性(Attribute) 的标志。其中值的含义如下:
0表示此点为图形的起始点;1表示此点为直线两个端点之其中一点;3表示此点为三次贝塞尔曲线的端点或控制点;0x7为遮罩所有位元,但表示点类型的三个低阶位不在此列;0x20指定此点为标记;0x80表示此点为封闭式子路径的最后一个点。

这是其原始的定义:
typedef enum  {
  PathPointTypeStart          = 0,
  PathPointTypeLine           = 1,
  PathPointTypeBezier         = 3,
  PathPointTypePathTypeMask   = 0x7,
  PathPointTypePathDashMode   = 0x10,
  PathPointTypePathMarker     = 0x20,
  PathPointTypeCloseSubpath   = 0x80,
  PathPointTypeBezier3        = 3 
} PathPointType;
我们来看看图2的GraphicsPath(通过C#写的小工具得到其背后发生的秘密,哈哈~~):

GraphicsPath path = new GraphicsPath(
new PointF[] {
new PointF(278F,99F),new PointF(278F,148.1533F),new PointF(238.1533F,188F),new PointF(189F,188F),new PointF(139.8466F,188F),
new PointF(100F,148.1533F),new PointF(100F,99F),new PointF(100F,49.84666F),new PointF(139.8466F,10F),new PointF(189F,10F),
new PointF(238.1533F,10F),new PointF(278F,49.84666F),new PointF(278F,99F),new PointF(408F,99F),new PointF(408F,148.1533F),
new PointF(368.1533F,188F),new PointF(319F,188F),new PointF(269.8466F,188F),new PointF(230F,148.1533F),new PointF(230F,99F),
new PointF(230F,49.84666F),new PointF(269.8466F,10F),new PointF(319F,10F),new PointF(368.1533F,10F),new PointF(408F,49.84666F),
new PointF(408F,99F)},
            new System.Byte[] { 0,3,3,3,3,3,3,3,3,3,3,3,131,0,3,3,3,3,3,3,3,3,3,3,3,131 });

注意上面最后一行那个Byte数组,里面有0,3,131等值,其中131是128(即0x80)+3的组合,说明该点为封闭子路径的最后一点及是贝塞尔曲线的端点。

下图3为关键点显示出来的样示(其中蓝色为关键点):

关于GDI+中GraphicsPath进行合并(Union)截切(Exclude)等编程的探讨(1)_第1张图片

图3  图形路径的关键点

通过上面可以看出:左右两个圆的交叉点位置并不是关键点!

麻烦就出来了,我们必须找到这些交叉点,然后,将交叉点也作为关键点放入最后的GraphicsPath中,这样才能解决图形路径的合并问题。

那么,如何找到交叉点呢?对于矩形(如下图4)、圆形等较规则的图形路径而言还比较好办,我们可以想办法通过数学公式来进行。

关于GDI+中GraphicsPath进行合并(Union)截切(Exclude)等编程的探讨(1)_第2张图片

图4  两个矩形构建的GraphicsPath(其中蓝色点为关键点)

图4的源码仅是将图2所示的代码中加粗的两行代码中的AddEllipse改为AddRectangle而已。很明显,求它们的交叉点是非常容易的。但是,对于不规则的贝塞尔曲线等则变得非常棘手了。

大家如果有兴趣,可以先看看这篇文章,比较基础,但非常有用,不过是英文的,如果你的英文不咋的,请耐着性子看下去,看明白了,一定会有收获的!

http://processingjs.nihongoresources.com/bezierinfo/

顺便还可以看看这个:

http://processingjs.nihongoresources.com/bezierinfo/sketchsource.php?sketch=cubicSubdivision

http://fei.edu.br/~psergio/CG_arquivos/IntroSplines.pdf

http://www.uio.no/studier/emner/matnat/ifi/INF3320/h03/undervisningsmateriale/lecture8.pdf

http://losingfight.com/blog/2011/07/08/how-to-implement-boolean-operations-on-bezier-paths-part-2/

http://www.cs.berkeley.edu/~hling/research/paper/surface.htm

http://blog.sina.com.cn/s/blog_640531380100q669.html


(未完待续,准备另起一篇专门介绍这个问题)

你可能感兴趣的:(关于GDI+中GraphicsPath进行合并(Union)截切(Exclude)等编程的探讨(1))