关于裁剪区域SetClip的初步理解
先看一个关于setclip用法的初步使用的例子:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Drawing; 6 using System.Windows.Forms; 7 using System.Drawing.Drawing2D; 8 9 namespace SetClipDemo 10 { 11 class SetClipDemo:Form 12 { 13 static void Main(string[] args) 14 { 15 Application.Run(new SetClipDemo()); 16 } 17 public SetClipDemo() 18 { 19 Text = "Set Clip Demo"; 20 ResizeRedraw = true; 21 } 22 23 protected override void OnPaint(PaintEventArgs e) 24 { 25 Graphics grfx = e.Graphics; 26 GraphicsPath path = new GraphicsPath(); 27 path.AddEllipse(0,0,ClientSize.Width/3 * 2,ClientSize.Height); 28 path.AddEllipse(ClientSize.Width / 3, 0, ClientSize.Width / 3 * 2, ClientSize.Height); 29 //grfx.DrawPath(new Pen(Color.Red), path); 30 31 grfx.SetClip(path); 32 33 grfx.FillPath(Brushes.Violet, path); 34 } 35 } 36 }
这个示例比较简单,就是画了两个椭圆,两个椭圆有交叉部分;而后,使用setclip进行区域裁剪,结果如下:
关于setclip用法的理解,书中有比较形象的解释,特引用如下
裁剪区域setclip生效以后,后续代码中的所涉及的内容,只能在所裁剪的区域中才能展现出来,如上述代码中的violet颜色,我们使用的代码是填充整个客户区的,但是裁剪区域之后,只能在裁剪区域中才可以看到violet颜色。
实际上,裁剪区域的本质是使用GraphicsPath创建的轮廓,经SetClip之后,在后台利用这个轮廓创建了一个Region区域对象,从而完成裁剪区域的工作的。
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
下面再看了稍微复杂点的,依然以上面的示例为基础模型
1 //--------------------------------------------------- 2 // ClippingCombinations.cs ?2001 by Charles Petzold 3 //--------------------------------------------------- 4 using System; 5 using System.Drawing; 6 using System.Drawing.Drawing2D; 7 using System.Windows.Forms; 8 using PrintableForm_chen; 9 10 11 class ClippingCombinations: PrintableForm 12 { 13 string strCaption = "CombineMode = "; 14 MenuItem miCombineMode; 15 16 public new static void Main() 17 { 18 Application.Run(new ClippingCombinations()); 19 } 20 public ClippingCombinations() 21 { 22 Text = strCaption + (CombineMode)0; 23 24 Menu = new MainMenu(); 25 Menu.MenuItems.Add("&CombineMode"); 26 27 EventHandler ehClick = new EventHandler(MenuCombineModeOnClick); 28 29 for (int i = 0; i < 6; i++) 30 { 31 MenuItem mi = new MenuItem("&" + (CombineMode)i); 32 mi.Click += ehClick; 33 mi.RadioCheck = true; 34 35 Menu.MenuItems[0].MenuItems.Add(mi); 36 } 37 miCombineMode = Menu.MenuItems[0].MenuItems[0]; 38 miCombineMode.Checked = true; 39 } 40 void MenuCombineModeOnClick(object obj, EventArgs ea) 41 { 42 miCombineMode.Checked = false; 43 miCombineMode = (MenuItem) obj; 44 miCombineMode.Checked = true; 45 46 Text = strCaption + (CombineMode)miCombineMode.Index; 47 Invalidate(); 48 } 49 protected override void DoPage(Graphics grfx, Color clr, int cx, int cy) 50 { 51 GraphicsPath path = new GraphicsPath(); 52 path.AddEllipse(0, 0, 2 * cx / 3, cy); 53 grfx.SetClip(path); 54 55 //path.Reset(); 56 path.AddEllipse(cx / 3, 0, 2 * cx / 3, cy); 57 grfx.SetClip(path, (CombineMode)miCombineMode.Index); 58 59 grfx.FillRectangle(Brushes.Red, 0, 0, cx, cy); 60 } 61 }
注意:第55行,将path.Reset();进行了注销;
下面是上述代码产生的结果,如下:
开始对为什么产生图片的结果进行分析
仅仅使用第一个setclip,产生如下效果
而两个椭圆叠加使用setclip(类似本篇第一个示例那样)之后,产生的效果如下
也就是说,在使用grfx.SetClip(path, (CombineMode)miCombineMode.Index);进行运算时,其它就是第一个裁剪区域和第二个裁剪区域的叠加运算
两个裁剪区域的叠加运算的计算规则如下:
明白了上述原理后,计算结果就非常明显了;再展示一下上述代码的计算结果
-------------------------------------------------------------------------------------------------------------------------------------------------------------------
上面是注释掉path.Reset();的情况,再看一下启用path.Reset();的情况
1 //--------------------------------------------------- 2 // ClippingCombinations.cs ?2001 by Charles Petzold 3 //--------------------------------------------------- 4 using System; 5 using System.Drawing; 6 using System.Drawing.Drawing2D; 7 using System.Windows.Forms; 8 using PrintableForm_chen; 9 10 11 class ClippingCombinations: PrintableForm 12 { 13 string strCaption = "CombineMode = "; 14 MenuItem miCombineMode; 15 16 public new static void Main() 17 { 18 Application.Run(new ClippingCombinations()); 19 } 20 public ClippingCombinations() 21 { 22 Text = strCaption + (CombineMode)0; 23 24 Menu = new MainMenu(); 25 Menu.MenuItems.Add("&CombineMode"); 26 27 EventHandler ehClick = new EventHandler(MenuCombineModeOnClick); 28 29 for (int i = 0; i < 6; i++) 30 { 31 MenuItem mi = new MenuItem("&" + (CombineMode)i); 32 mi.Click += ehClick; 33 mi.RadioCheck = true; 34 35 Menu.MenuItems[0].MenuItems.Add(mi); 36 } 37 miCombineMode = Menu.MenuItems[0].MenuItems[0]; 38 miCombineMode.Checked = true; 39 } 40 void MenuCombineModeOnClick(object obj, EventArgs ea) 41 { 42 miCombineMode.Checked = false; 43 miCombineMode = (MenuItem) obj; 44 miCombineMode.Checked = true; 45 46 Text = strCaption + (CombineMode)miCombineMode.Index; 47 Invalidate(); 48 } 49 protected override void DoPage(Graphics grfx, Color clr, int cx, int cy) 50 { 51 GraphicsPath path = new GraphicsPath(); 52 path.AddEllipse(0, 0, 2 * cx / 3, cy); 53 grfx.SetClip(path); 54 55 path.Reset(); 56 path.AddEllipse(cx / 3, 0, 2 * cx / 3, cy); 57 grfx.SetClip(path, (CombineMode)miCombineMode.Index); 58 59 grfx.FillRectangle(Brushes.Red, 0, 0, cx, cy); 60 } 61 }
有与无path.Reset();产生的结果还是不一样的
我们先看一下path.Reset();在CSDN中的定义:
----------------------------------------------------------------------------------------------------------------
GraphicsPath.Reset 方法
清空 PathPoints 和 PathTypes 数组并将 FillMode 设置为 Alternate。
public void Reset ();
---------------------------------------------------------------------------------------------
为此,我添加了大量的验证代码进行验证,验证代码如下:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Drawing; 6 using System.Windows.Forms; 7 using System.Drawing.Drawing2D; 8 using PrintableForm_chen; 9 10 namespace ClippingCombinations 11 { 12 class ClippingCombinations:PrintableForm 13 { 14 string strCaption = "CombineMode ="; 15 MenuItem miCombineMode; 16 17 static void Main(string[] args) 18 { 19 Application.Run(new ClippingCombinations()); 20 } 21 public ClippingCombinations() 22 { 23 Text = strCaption + (CombineMode)0; 24 25 Menu = new MainMenu(); 26 Menu.MenuItems.Add("&CombineMode"); 27 28 EventHandler ehClick = new EventHandler(MenuCombineModeOnClick); 29 30 for (int i = 0; i < 6; i++) 31 { 32 MenuItem mi = new MenuItem("&" + (CombineMode)i); 33 mi.Click += ehClick; 34 mi.RadioCheck = true; 35 Menu.MenuItems[0].MenuItems.Add(mi); 36 } 37 38 miCombineMode = Menu.MenuItems[0].MenuItems[0]; 39 miCombineMode.Checked = true; 40 } 41 42 void MenuCombineModeOnClick(object obj, EventArgs e) 43 { 44 miCombineMode.Checked = false; 45 miCombineMode = (MenuItem)obj; 46 miCombineMode.Checked = true; 47 48 Text = strCaption + (CombineMode)miCombineMode.Index; 49 Invalidate(); 50 } 51 52 protected override void DoPage(Graphics grfx, Color clr, int cx, int cy) 53 { 54 GraphicsPath path = new GraphicsPath(); 55 path.AddEllipse(0,0,2 *cx/3,cy); 56 //测试使用 57 grfx.DrawString("此行用于创建多行文字,用于测试界面的空间,验证相关内容的准确性!", Font, Brushes.Red, 0,0); 58 grfx.DrawString("此行用于创建多行文字,用于测试界面的空间,验证相关内容的准确性!", Font, Brushes.Red, 0, 50); 59 grfx.DrawString("此行用于创建多行文字,用于测试界面的空间,验证相关内容的准确性!", Font, Brushes.Red, 0, 100); 60 grfx.DrawLine(new Pen(Color.Red, 5), 0, 0, cx, cy); 61 //继续测试路径的数组坐标 62 Console.WriteLine("这是第一个椭圆的路径点信息总量:{0}",path.PathPoints.Length); 63 Console.WriteLine(path.PointCount); 64 for (int i = 0; i < path.PathPoints.Length; i++) 65 { 66 Console.WriteLine(path.PathPoints[i]); 67 68 //grfx.DrawLine(new Pen(Color.Green), cx / 2, cy / 2, path.PathPoints[i].X, path.PathPoints[i].Y); 69 } 70 //测试:第一个椭圆,描绘路径 71 float width1 = 10; 72 //grfx.DrawPath(new Pen(Brushes.Red,width1),path); 73 74 //测试:第一个椭圆,填充路径 75 //grfx.FillPath(Brushes.Black,path); 76 77 grfx.SetClip(path); 78 //测试:第一个椭圆裁剪之后,填充背景为粉色 79 grfx.FillRectangle(Brushes.Pink, 0, 0, cx, cy); 80 81 //测试:第一个椭圆裁剪之后,描绘路径 82 //grfx.DrawPath(new Pen(Brushes.Green, width1), path); 83 84 //测试:第一个椭圆裁剪之后,填充路径 85 //grfx.FillPath(Brushes.Black, path); 86 87 //PathData pathdata = new PathData(); 88 //pathdata = path.PathData; 89 //Console.WriteLine("使用PathData类型,{0}", pathdata.Points[0].X); 90 91 path.Reset(); 92 93 //PathData pathdata = new PathData(); 94 //pathdata = path.PathData; 95 //Console.WriteLine("使用PathData类型,{0}", pathdata.Points[0].X); 96 97 path.AddEllipse(cx/3,0,2 * cx/3,cy); 98 //测试:添加第二个椭圆之后,描绘路径 99 //grfx.DrawPath(new Pen(Brushes.Cyan, width1), path); 100 101 //测试:添加第二个椭圆之后,填充路径 102 //grfx.FillPath(Brushes.Black, path); 103 104 105 106 //测试使用 107 grfx.DrawString("此行用于创建多行文字,用于测试界面的空间,验证相关内容的准确性!", Font, Brushes.Purple, 0, 25); 108 grfx.DrawString("此行用于创建多行文字,用于测试界面的空间,验证相关内容的准确性!", Font, Brushes.Purple, 0, 75); 109 grfx.DrawString("此行用于创建多行文字,用于测试界面的空间,验证相关内容的准确性!", Font, Brushes.Purple, 0, 125); 110 grfx.DrawLine(new Pen(Color.Purple, 5), cx,0, 0, cy); 111 112 //测试路径的数组并输出数组 113 Console.WriteLine(path.PathPoints.Length); 114 Console.WriteLine(path.PointCount); 115 for (int i = 0; i < path.PathPoints.Length; i++) 116 { 117 Console.WriteLine(path.PathPoints[i]); 118 119 grfx.DrawLine(new Pen(Color.Green),cx/2,cy/2,path.PathPoints[i].X,path.PathPoints[i].Y); 120 } 121 grfx.DrawLines(new Pen(Color.Green),path.PathPoints); 122 //至此,以上均为测试内容 123 124 //grfx.SetClip(path); 125 126 //grfx.DrawPath(new Pen(Color.Violet), path); 127 128 grfx.SetClip(path,(CombineMode)miCombineMode.Index); 129 //测试:第二次裁剪之后,路径数组的数量 130 Console.WriteLine("第二次裁剪之后,路径数组的数量是:{0}",path.PathPoints.Length); 131 132 //测试:第二次裁剪之后,描绘路径 133 //grfx.DrawPath(new Pen(Brushes.Blue, width1), path); //注:此行代码中没有发现路径 134 135 //测试:第二次裁剪之后,填充路径 136 //grfx.FillPath(Brushes.Black, path); 137 138 grfx.FillRectangle(Brushes.Yellow,0,0,cx,cy); 139 140 grfx.DrawString("此行用于创建多行文字,用于测试界面的空间,验证相关内容的准确性!",Font,Brushes.Blue,0,150); 141 grfx.DrawString("此行用于创建多行文字,用于测试界面的空间,验证相关内容的准确性!", Font, Brushes.Blue, 0, cy-15); 142 } 143 } 144 }
在上述代码的验证中,在使用了path.Reset();之后,包括path.pathdata,path.PathPoints.Length等等都没有任何数据,确实已经清空了;
启用第93-95行的代码时,程序会报错,就是因为 path.Reset();之后,第一个椭圆的路径数据已经被清空。
但是,第二个椭圆的相关路径数据还存在的,这就造成第一个裁剪区域是左边的椭圆,第二个裁剪区域是右边的椭圆,两个裁剪区域如下:
整理出第一裁剪区域和第二裁剪区域,再进行grfx.SetClip(path,(CombineMode)miCombineMode.Index);运算就没有问题了!
再次展示一下效果
using System;using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Windows.Forms;
using System.Drawing.Drawing2D;
using PrintableForm_chen;
namespace ClippingCombinations
{
class ClippingCombinations:PrintableForm
{
string strCaption = "CombineMode =";
MenuItem miCombineMode;
static void Main(string[] args)
{
Application.Run(new ClippingCombinations());
}
public ClippingCombinations()
{
Text = strCaption + (CombineMode)0;
Menu = new MainMenu();
Menu.MenuItems.Add("&CombineMode");
EventHandler ehClick = new EventHandler(MenuCombineModeOnClick);
for (int i = 0; i < 6; i++)
{
MenuItem mi = new MenuItem("&" + (CombineMode)i);
mi.Click += ehClick;
mi.RadioCheck = true;
Menu.MenuItems[0].MenuItems.Add(mi);
}
miCombineMode = Menu.MenuItems[0].MenuItems[0];
miCombineMode.Checked = true;
}
void MenuCombineModeOnClick(object obj, EventArgs e)
{
miCombineMode.Checked = false;
miCombineMode = (MenuItem)obj;
miCombineMode.Checked = true;
Text = strCaption + (CombineMode)miCombineMode.Index;
Invalidate();
}
protected override void DoPage(Graphics grfx, Color clr, int cx, int cy)
{
GraphicsPath path = new GraphicsPath();
path.AddEllipse(0,0,2 *cx/3,cy);
//测试使用
grfx.DrawString("此行用于创建多行文字,用于测试界面的空间,验证相关内容的准确性!", Font, Brushes.Red, 0,0);
grfx.DrawString("此行用于创建多行文字,用于测试界面的空间,验证相关内容的准确性!", Font, Brushes.Red, 0, 50);
grfx.DrawString("此行用于创建多行文字,用于测试界面的空间,验证相关内容的准确性!", Font, Brushes.Red, 0, 100);
grfx.DrawLine(new Pen(Color.Red, 5), 0, 0, cx, cy);
//继续测试路径的数组坐标
Console.WriteLine("这是第一个椭圆的路径点信息总量:{0}",path.PathPoints.Length);
Console.WriteLine(path.PointCount);
for (int i = 0; i < path.PathPoints.Length; i++)
{
Console.WriteLine(path.PathPoints[i]);
//grfx.DrawLine(new Pen(Color.Green), cx / 2, cy / 2, path.PathPoints[i].X, path.PathPoints[i].Y);
}
//测试:第一个椭圆,描绘路径
float width1 = 10;
//grfx.DrawPath(new Pen(Brushes.Red,width1),path);
//测试:第一个椭圆,填充路径
//grfx.FillPath(Brushes.Black,path);
grfx.SetClip(path);
//测试:第一个椭圆裁剪之后,填充背景为粉色
grfx.FillRectangle(Brushes.Pink, 0, 0, cx, cy);
//测试:第一个椭圆裁剪之后,描绘路径
//grfx.DrawPath(new Pen(Brushes.Green, width1), path);
//测试:第一个椭圆裁剪之后,填充路径
//grfx.FillPath(Brushes.Black, path);
//PathData pathdata = new PathData();
//pathdata = path.PathData;
//Console.WriteLine("使用PathData类型,{0}", pathdata.Points[0].X);
path.Reset();
//PathData pathdata = new PathData();
//pathdata = path.PathData;
//Console.WriteLine("使用PathData类型,{0}", pathdata.Points[0].X);
path.AddEllipse(cx/3,0,2 * cx/3,cy);
//测试:添加第二个椭圆之后,描绘路径
//grfx.DrawPath(new Pen(Brushes.Cyan, width1), path);
//测试:添加第二个椭圆之后,填充路径
//grfx.FillPath(Brushes.Black, path);
//测试使用
grfx.DrawString("此行用于创建多行文字,用于测试界面的空间,验证相关内容的准确性!", Font, Brushes.Purple, 0, 25);
grfx.DrawString("此行用于创建多行文字,用于测试界面的空间,验证相关内容的准确性!", Font, Brushes.Purple, 0, 75);
grfx.DrawString("此行用于创建多行文字,用于测试界面的空间,验证相关内容的准确性!", Font, Brushes.Purple, 0, 125);
grfx.DrawLine(new Pen(Color.Purple, 5), cx,0, 0, cy);
//测试路径的数组并输出数组
Console.WriteLine(path.PathPoints.Length);
Console.WriteLine(path.PointCount);
for (int i = 0; i < path.PathPoints.Length; i++)
{
Console.WriteLine(path.PathPoints[i]);
grfx.DrawLine(new Pen(Color.Green),cx/2,cy/2,path.PathPoints[i].X,path.PathPoints[i].Y);
}
grfx.DrawLines(new Pen(Color.Green),path.PathPoints);
//至此,以上均为测试内容
//grfx.SetClip(path);
//grfx.DrawPath(new Pen(Color.Violet), path);
grfx.SetClip(path,(CombineMode)miCombineMode.Index);
//测试:第二次裁剪之后,路径数组的数量
Console.WriteLine("第二次裁剪之后,路径数组的数量是:{0}",path.PathPoints.Length);
//测试:第二次裁剪之后,描绘路径
//grfx.DrawPath(new Pen(Brushes.Blue, width1), path); //注:此行代码中没有发现路径
//测试:第二次裁剪之后,填充路径
//grfx.FillPath(Brushes.Black, path);
grfx.FillRectangle(Brushes.Yellow,0,0,cx,cy);
grfx.DrawString("此行用于创建多行文字,用于测试界面的空间,验证相关内容的准确性!",Font,Brushes.Blue,0,150);
grfx.DrawString("此行用于创建多行文字,用于测试界面的空间,验证相关内容的准确性!", Font, Brushes.Blue, 0, cy-15);
}
}
}