图形图像编程
学习使用Graphics类中对象(画笔、刷子等)和各种方法,以及Bitmap类的应用。
5.1 图形设备环境接口(GDI)
为了在Windows窗口输出数据(字符或图形),Windows操作系统提供了一些工具和函数,例如提供笔用来定义图形外轮廓线的颜色及粗细,提供刷子定义添充封闭图形内部的颜色和格式,提供不同输出字体,提供函数用来输出字符或绘制图形等等。所有这些工具和函数被放在图形设备接口函数库中(GDI32.DLL),它负责CRT显示及打印。根据设备不同,可以构造不同的设备环境(GDI),所有GDI对用户程序有相同的接口,既无论是在CRT显示还是在打印机上打印同一个图形或字符,都用相同的函数。GDI所扮演的角色如下图所示:
CRT显示 |
打印机打印 |
CRT驱动程序 |
打印机驱动程序 |
GDI 设备环境 |
用户 应用程序 |
用户应用程序根据是在CRT显示还是在打印机打印,首先选择CRT显示GDI或打印GDI,然后调用GDI中的同名函数实现在CRT显示或在打印机上打印。而GDI根据选择的不同设备,调用不同的设备驱动程序,在CRT上显示或在打印机上打印。而这些驱动程序都是各个设备制造厂商提供的。这样做的最大好处是应用程序和设备无关,应用程序不必为不同的设备编制不同的程序。为使用不同的设备,无论是不同的显卡,还是不同的打印机,只要安装该设备的驱动程序,应用程序就可以使用该设备了,微软的Word程序可以使用不同的打印机就是使用了这个原理。
.Net系统的基础类库对Windows操作系统的图形设备接口函数库(GDI32.DLL)进行了扩充,并用类进行了封装,一般叫做GDI+。使用GDI+绘图更加方便快捷,本章重点介绍GDI+的使用。为了使用GDI+图形功能,必须引入以下命名空间:System.Drawing、System.Drawing.Priniting、System.Drawing.Imaging、System.Drawing.Drawing2D,System.Drawing.Design、System.Drawing.Text。
5.2 Graphics类
System.Drawing.Graphics类对GDI+进行了封装,Graphics类提供一些方法完成各种图形的绘制。Graphics类对象与特定的控件或设备关联,为了在不同的控件或设备上用完全相同的代码完成同样的图形,应根据不同的控件或设备建立或得到该控件或设备的Graphics类对象。Graphics类是密封类,不能有派生类。
5.2.1 使用Graphics类绘图的基本步骤
GDI+大部分功能被封装在Graphics类中,Graphics类提供了一些工具和函数,例如提供笔用来定义图形外轮廓线的颜色及粗细,提供刷子定义添充封闭图形内部的颜色和格式,提供不同输出字体,提供函数用来输出字符或绘制图形等等。为了在窗体中或其它控件中使用这些工具和函数绘图,必须首先得到这些窗体或控件使用的Graphics类对象。下面的例子,在窗体中增加了一个按钮,单击按钮将在窗体中画一个边界为红色,内部填充为蓝色的圆。该程序段说明了使用Graphics类绘图的基本步骤。按钮的单击事件处理函数如下:
private void button1_Click(object sender,System.EventArgs e)
{ Graphics g=this.CreateGraphics();//得到窗体使用的Graphics类对象
Pen pen1=new Pen(Color.Red);//创建红色笔对象
SolidBrush brush1=new SolidBrush(Color.Blue);//创建蓝色刷子对象
g.DrawEllipse(pen1,10,10,100,100);//用红色笔画圆的边界
g.FillEllipse(brush1,10,10,100,100);//用蓝色刷子填充圆的内部
}
运行后,单击按钮,出现边界为红色,内部填充为蓝色的圆。
5.2.2 窗体的Paint事件
运行上例,单击按钮,出现边界为红色,内部填充为蓝色的圆。最小化后,再最大化,图形消失。这是因为窗体用户区内容可能被破坏,例如窗体最小化后,再最大化;菜单被打开再关闭;打开对话框再关闭等,用户区内容被覆盖。操作系统不保存被破坏的用户区内容,而是由应用程序自己恢复被破坏的用户区内容。当应用程序窗口用户区内容被破坏后需恢复时,Windows操作系统向应用程序发送Paint事件,应用程序应把在窗口用户区输出数据的语句放在Paint事件处理函数中,应用程序响应Paint事件,能在事件处理函数中调用这些在窗口用户区输出数据的语句恢复被破坏的内容。主窗体Form不能自动响应Paint事件,程序员必须生成Paint事件处理函数。修改上例,增加主窗体的Paint事件处理函数如下:
private void Form1_Paint(object sender,System.Windows.Forms.PaintEventArgs e)
{ Graphics g=e.Graphics;//得到窗体的使用的Graphics类对象
Pen pen1=new Pen(Color.Red);
SolidBrush brush1=new SolidBrush(Color.Blue);
g.DrawEllipse(pen1,10,10,100,100);
g.FillEllipse(brush1,10,10,100,100);
}
运行后,出现边界为红色,内部填充蓝色的圆。最小化后,再最大化,图形不消失。
5.3 GDI+中三种坐标系统:
GDI+定义了三种坐标系统,并提供了三种坐标转换的方法Graphics.TransformPoints()。
l 页面(Page)坐标系统:窗体用户区左上角为原点,横向x轴向右为正方向,纵向y轴向下为正方向。单位为像素。这是默认的坐标系统。
l 全局坐标系统:整个显示屏幕的左上角为原点,其余设置和页面坐标系统相同。
l 设备坐标系统:使用英寸、毫米等用户设置的单位,其余设置和页面坐标系统相同。如果单位为像素,就是页面(Page)坐标系统。
5.4 GDI+中常用的结构
本节介绍GDI+中常用的结构,包括:Point、PointF、Size、SizeF、Rectangle、RectangleF、Color等。它们是在命名空间System.Drawing中定义的。
5.4.1 点结构Point和PointF
点结构有两个成员:X,Y,表示点的x轴和y轴的坐标。其常用构造函数如下:
Point p1=new Point(int X,int Y);//X,Y为整数
PointF p2=new PointF(float X,floa Y);//X,Y为浮点数
5.4.2 结构Size和SizeF
结构Size和SizeF用来描述对象尺寸,有成员:Width和Height。常用构造函数如下:
public Size(int width,int height);
public SizeF(float width,float height);
5.4.3 矩形结构Rectangle和RectangleF
矩形结构Rectangle和RectangleF用来描述一个矩形,其常用属性如下:
l Left和Top:矩形结构对象左上角的x坐标和y坐标。
l Right和Bottom:矩形结构对象右下角的x坐标和y坐标。
l Width和Height:矩形结构对象的宽度和高度。
l Size:描述矩形结构对象的尺寸。
l X和Y: 矩形结构对象左上角的x坐标和y坐标。
注意只有属性X、Y、Width和Height是可读写的,其余属性是只读的。常用构造函数为:
//参数为矩形左上角坐标的点结构location和代表矩形宽和高的Size结构size
Rectangle(Point location,Size size);//参数也可为PointF和SizeF
//参数为矩形左上角x和y坐标,宽,高
Rectangle(int X,int Y,int width,int height);//X和Y也可为float
5.4.4 结构Color
Color结构用来描述颜色。任何一种颜色可以用透明度(al),蓝色(bb),绿色(gg),红色(rr)合成,16进制数格式为0xalrrbbgg,其中al,bb,gg,rr为2位16进制数(0-255),用这个无符号32位数代表颜色。常用方法如下:
l public static Color FromArgb(int alpha,int rr,int gg,int bb);
用四个分量(透明度、红色、绿色和蓝色)值创建新颜色。每个参数分量的值要求小于256。alpha值表示透明度,=0为完全透明,=255为完全不透明
l public static Color FromArgb(int rr,int gg,int bb);
用指定的颜色值(红色、绿色和蓝色)创建新颜色。透明度值默认为255(完全不透明)。每个参数分量的值都要求小于256。红色为FromArgb(255,0,0),绿色为FromArgb(0,255,0),蓝色为FromArgb(0,0,255)。
l public static Color FromArgb(int alpha,Color color);
创建新颜色,颜色为参数2指定颜色,透明度值为参数1指定的值,要求alpha小于256。透明度及颜色使用的例子如下,该例创建3个半透明的红、绿、蓝刷子,填充3个矩形。
private void Form1_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{ Graphics g=e.Graphics;
SolidBrush RedBrush=new SolidBrush(Color.FromArgb(128,255,0,0));//半透明
SolidBrush GreenBrush=new SolidBrush(Color.FromArgb(128,0,255,0));
SolidBrush BlueBrush=new SolidBrush(Color.FromArgb(128,0,0,255));
g.FillRectangle(RedBrush,0,0,80,80);
g.FillRectangle(GreenBrush,40,0,80,80);
g.FillRectangle(BlueBrush,20,20,80,80);
}
效果如右图,可以将透明度alpha值设为255,再运行一次,看看有何不同。C#中还预定义了一些颜色常数,例如黑色为Color.Black,红色为Color.Red等等,读者可用帮助察看。
5.5 画笔Pen类
Pen类对象指定绘制图形的外轮廓线宽度和颜色。Pen类有4个构造函数,分别是:
l public Pen(Color color);//建立颜色为color的笔,宽度默认为1
l public Pen(Color color,float width);//建立颜色为color的笔,宽度为width
l public Pen(Brush brush);//使用刷子为笔
l public Pen(Brush,float width);//使用刷子为笔,宽度为width
Pen类常用的属性:Color为笔的颜色,Width为笔的宽度,DashStyle为笔的样式,EndCap和StartCap为线段终点和起点的外观。下例显示各种笔的DashStyle、EndCap和StartCap不同选项的样式(见下图)。主窗体Paint事件处理函数如下:
private void Form1_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{ Graphics g=e.Graphics;
Pen pen1=new Pen(Color.Red,6);//默认为实线笔
g.DrawLine(pen1,10,10,100,10);//画实线,图中左边第1条线
pen1.DashStyle=System.Drawing.Drawing2D.DashStyle.Dash;//虚线笔
g.DrawLine(pen1,10,20,100,20);//画虚线,图中左边第2条线
pen1.DashStyle=System.Drawing.Drawing2D.DashStyle.DashDot;//点,短线风格的线
g.DrawLine(pen1,10,30,100,30);//图中左边第3条线
//双点,短线风格的线
pen1.DashStyle=System.Drawing.Drawing2D.DashStyle.DashDotDot;
g.DrawLine(pen1,10,40,100,40);//图中左边第4条线
pen1.DashStyle=System.Drawing.Drawing2D.DashStyle.Dot;//由点组成的线
g.DrawLine(pen1,10,50,100,50);//图中左边第5条线
pen1.DashStyle=System.Drawing.Drawing2D.DashStyle.Solid;//恢复实线笔
pen1.EndCap=System.Drawing.Drawing2D.LineCap.ArrowAnchor;//后箭头
g.DrawLine(pen1,150,10,250,10);//图中右边第1条线
pen1.StartCap=System.Drawing.Drawing2D.LineCap.ArrowAnchor;//前箭头
g.DrawLine(pen1,150,22,250,22);//图中右边第2条线
pen1.EndCap=System.Drawing.Drawing2D.LineCap.RoundAnchor;
g.DrawLine(pen1,150,34,250,34);//图中右边第3条线
pen1.EndCap=System.Drawing.Drawing2D.LineCap.SquareAnchor;
g.DrawLine(pen1,150,46,250,46);//图中右边第4条线
pen1.EndCap=System.Drawing.Drawing2D.LineCap.Triangle;
g.DrawLine(pen1,150,58,250,58);//图中右边第5条线
pen1.EndCap=System.Drawing.Drawing2D.LineCap.DiamondAnchor;
//图中右边第6条线
g.DrawLine(pen1,150,70,250,70);
}
运行效果如右图。
5.6 创建画刷
画刷类对象指定填充封闭图形内部的颜色和样式,封闭图形包括矩形、椭圆、扇形、多边形和任意封闭图形。GDI+系统提供了几个预定义画刷类,包括:
l SolidBrush:单色画刷,在命名空间System.Drawing中定义。
l HatchBrush:阴影画刷,后4个画刷在命名空间System.Drawing.Drawing2D中定义。
l TextureBrush:纹理(图像)画刷。
l LinearGradientBrush:两个颜色或多个颜色线性渐变画刷。
l PathGradientBrush:使用路径定义刷子形状的复杂渐变画刷。
5.6.1 单色画刷SolidBrush
前边已使用过单色画刷。其构造函数只有1个,定义如下:
SolidBrush brush1=new SolidBrush(Colorcolor);//建立指定颜色的画刷
可以使用属性Color来修改画刷颜色,例如:brush1.Color=Color.Green;
5.6.2 阴影画刷HatchBrush
用指定样式(例如,多条横线、多条竖线、多条斜线等)、指定填充线条的颜色和指定背景颜色定义的画刷,阴影画刷有两个构造函数:
//指定样式和填充线条的颜色的构造函数,背景色被初始化为黑色。
HatchBrush brush1=new HatchBrush(HatchStyle h,Color c);
//指定样式、填充线条的颜色(c1)和背景颜色(c2)的构造函数。
HatchBrush brush1=new HatchBrush(HatchStyle h,Color c1,Color c2);
常用属性如下:
l 属性backgroundColor:画刷背景颜色。
l 属性foreColor:画刷填充线条的颜色。
l 属性HatchStyle:该属性是只读的,不能修改,表示画刷的不同样式。
例子e5_6_2:显示了阴影画刷属性HatchStyle为不同值时画刷的不同样式。在Form1.cs文件头部增加语句:using System.Drawing.Drawing2D,主窗体Paint事件处理函数如下:
private void Form1_Paint(object sender,System.Windows.Forms.PaintEventArgs e)
{ Graphics g=e.Graphics;//得到窗体的使用的Graphics类对象
HatchBrush b1=new
HatchBrush(HatchStyle.BackwardDiagonal,Color.Blue,Color.LightGray);
g.FillRectangle(b1,10,10,50,50);//矩形被填充左斜线,下图窗体中第1图
HatchBrush b2=new HatchBrush(HatchStyle.Cross,Color.Blue,Color.LightGray);
g.FillRectangle(b2,70,10,50,50);//矩形被填充方格,第2图
HatchBrush b3=new
HatchBrush(HatchStyle.ForwardDiagonal,Color.Blue,Color.LightGray);
g.FillRectangle(b3,130,10,50,50);//矩形被填充右斜线,第3图
HatchBrush b4=new
HatchBrush(HatchStyle.DiagonalCross,Color.Blue,Color.LightGray);
g.FillRectangle(b4,190,10,50,50);//矩形被填充菱形,第4图
HatchBrush b5=new
HatchBrush(HatchStyle.Vertical,Color.Blue,Color.LightGray);
g.FillRectangle(b5,250,10,50,50);//矩形被填充竖线,第5图
HatchBrush b6=new
HatchBrush(HatchStyle.Horizontal,Color.Blue,Color.LightGray);
g.FillRectangle(b6,310,10,50,50);//矩形被填充横线,第6图
}
运行效果如右图。
5.6.3 纹理(图像)画刷TextureBrush
纹理(图像)画刷使用图像来填充封闭曲线的内部,有8个构造函数,最简单的构造函数如下,其余请读者使用MSDN或VS.Net帮助查看。
TextureBrush(Image bitmap);//参数为画刷使用的位图类对象
下边的例子使用位图文件n2k.bmp建立位图类对象作为画刷的图案,在Form1.cs文件的头部增加语句:using System.Drawing.Drawing2D,主窗体Paint事件处理函数如下:
private void Form1_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{ Graphics g=e.Graphics;//得到窗体使用的Graphics类对象
Pen pen1=new Pen(Color.Red);
//位图类对象作为画刷图案,使用位图文件n2k.bmp建立位图类对象见5.10节
TextureBrush b1=
new TextureBrush(new Bitmap("C:\\WINNT\\system32\\n2k.bmp"));
g.FillRectangle(b1,10,10,200,100);
g.DrawRectangle(pen1,10,10,200,100);
}
文件C:\WINNT\system32\n2k.bmp定义的图形的显示效果为:应用程序运行效果如右图。
5.6.4 颜色渐变画刷LinearGradientBrush
该类封装双色渐变和自定义多色渐变画刷。所有颜色渐变都是沿矩形的宽度或两个点指定的直线变化的。默认情况下,双色渐变是沿指定直线从起始色到结束色的均匀水平线性变化。有8个构造函数,最简单的构造函数如下,其余请读者用帮助查看。
public LinearGradientBrush(//point1和point2作为线性渐变线段开始点和结束点
Point point1,Point point2,//也可以为PointF结构类型
Color color1,Color color2);//线性渐变开始颜色和结束颜色
下边的例子显示了不同开始点和结束点的线性渐变直线,使用颜色渐变画刷从黄渐变到蓝的效果。在Form1.cs文件的头部增加语句:using System.Drawing.Drawing2D,主窗体Paint事件处理函数如下:
private void Form1_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{ Graphics g=e.Graphics;//得到窗体的使用的Graphics类对象
Pen pen1=new Pen(Color.Red);
Point p1=new Point(10,10);//p1作为渐变直线开始点,也可以为PointF
Point p2=new Point(50,10);//p2作为渐变直线结束点,也可以为PointF
LinearGradientBrush brush1=//从黄渐变到蓝,见下图左图
new LinearGradientBrush(p1,p2,Color.Yellow,Color.Blue);
g.FillRectangle(brush1,10,10,200,100);
g.DrawRectangle(pen1,10,10,200,100);
p1=new Point(220,10);
p2=new Point(270,50);
LinearGradientBrush brush2=//从黄渐变到蓝,见下图右图
new LinearGradientBrush(p1,p2,Color.Yellow,Color.Blue);
g.FillRectangle(brush2,230,10,200,100);
g.DrawRectangle(pen1,230,10,200,100);
}
运行效果如右图。
5.6.5 画刷PathGradientBrush
画刷PathGradientBrush可以实现复杂的渐变颜色。有5个构造函数,这里只介绍其中的一个,其参数GraphicsPath类使用见本节例子及5.7.11节。其余构造函数请用帮助查看。
public PathGradientBrush(GraphicsPath path);//画刷PathGradientBrush构造函数
画刷属性SurroundColors:一个Color结构的数组,数组的每个元素与定义刷子外轮廓线路径的点数组中索引号相同的点相关联,描述刷子的各种颜色如何分布。
例子e5_6_5:介绍画刷PathGradientBrush的实现方法,在Form1.cs文件的头部增加语句:using System.Drawing.Drawing2D,主窗体Paint事件处理函数如下,运行效果如下图。
private void Form1_Paint(object sender,System.Windows.Forms.PaintEventArgs e)
{ Graphics g=e.Graphics;//得到窗体使用的Graphics类对象
Point[] p1=new Point[6];//点结构数组,有6个元素
p1[0]=new Point(30,0);
p1[1]=new Point(160,30);
p1[2]=new Point(90,60);
p1[3]=new Point(100,90);
p1[4]=new Point(30,60);
p1[5]=new Point(0,30);
GraphicsPath path=new GraphicsPath(//建立GraphicsPath类对象,见5.7.11节
p1,//由点数组p1定义绘制刷子的外轮廓线的路径
new Byte[]{//定义数组p1每个点元素的关联曲线类型,第1点是开始点
//贝塞尔曲线必须由4点组成,因此第1、2、3、4点组成一条贝塞尔曲线,
(byte)PathPointType.Start,(byte)PathPointType.Bezier,
(byte)PathPointType.Bezier,(byte)PathPointType.Bezier,
//第5、6点的类型是直线,因此第4点到第5点和从第5点到第6点是线段
(byte)PathPointType.Line,(byte)PathPointType.Line,});
//为了形成闭合曲线,增加最后一条线段
PathGradientBrush brush1=new PathGradientBrush(path);//生成画刷
brush1.SurroundColors=new Color[]{Color.Green,Color.Yellow,Color.Red,
Color.Blue,Color.Orange,Color.OliveDrab};//设置属性SurroundColors的值
g.FillPath(brush1,path);
}//上图显示的是刷子的形状和颜色的分布。
5.7 基本图形的绘制和填充
Graphics类提供了一些方法,用来绘制或填充各种图形。本节介绍这些方法。
5.7.1 用DrawLine方法绘制线段
第1个和第2个方法绘制一条线段,第3个方法绘制多条线段,方法定义如下:
l void DrawLine(Pen pen,int x1,int y1,int x2,int y2);
其中pen为画笔,(x1,y1)为画线起点坐标,(x2,y2)为画线终点坐标。
l DrawLine(Pen pen,Point p1,Point p2);
其中pen为画笔,点p1为画线起点坐标,点p2为画线终点坐标。
l public void DrawLines(Pen pen,Point[] points);
此方法绘制多条线段。从points[0]到points[1]画第1条线,从points[1]到points[2]画第2条线,依此类推。
例子e5_7_1A:使用DrawLine()的例子,为主窗体Paint事件增加事件处理函数如下:
private void Form1_Paint(object sender,System.Windows.Forms.PaintEventArgs e)
{ Graphics g=e.Graphics;
Pen pen1=new Pen(Color.Red);
g.DrawLine(pen1,30,30,100,100);//用笔pen1从点(30,30)到(100,100)画直线
Point p1=new Point(30,40);
Point p2=new Point(100,110);
g.DrawLine(pen1,p1,p2);//用笔pen1从点(30,40)到(100,110)画直线
}
例子e5_7_1B:使用绘制线段函数画任意曲线(画正弦曲线,注意如何使用数学函数)。
private void Form1_Paint(object sender,System.Windows.Forms.PaintEventArgs e)
{ Graphics g=this.CreateGraphics();//得到窗体的使用的Graphics类对象
Pen pen1=new Pen(Color.Red);
float y=50,y1,x1,x2;
for(int x=0;x<720;x++)//画2个周期正弦曲线
{ x1=(float)x;
x2=(float)(x+1);
y1=(float)(50+50*Math.Sin((3.14159/180.0)*(x+1)));
g.DrawLine(pen1,x1,y,x2,y1);
y=y1;
}
}
运行,在窗体中可以看到一条红色正弦曲线如下图。
例子e5_7_1C:在Windows画图程序中,可以拖动鼠标画任意曲线。本例实现用拖动鼠标左键在主窗体中画曲线。每条曲线都是由若干很短的线段组成。鼠标左键在按下状态,移动鼠标,每移动很短距离,画出这段线段,所有这些线段组合起来,形成一条曲线。
(1) 新建项目。在Form1类中增加两个私有变量:
private bool mark=false;//表示鼠标左键是否按下,如按下鼠标再移动将画曲线
private Point point;//记录画下一条很短线段的起始点。
(2) 为主窗体的事件OnMouseDown,OnMouseUp,OnMouseMove增加事件处理函数如下:
private void Form11_MouseDown(object sender,//鼠标按下事件处理函数
System.Windows.Forms.MouseEventArgs e)
{ if(e.Button==MouseButtons.Left)//如果鼠标左键按下
{ point.X=e.X;//记录曲线的第一个点的坐标,为画线段的起始点
point.Y=e.Y;
mark=true;//表示鼠标左键已按下,鼠标如果再移动,将画曲线
}
}
private void Form1_MouseMove(object sender,//鼠标移动事件处理函数
System.Windows.Forms.MouseEventArgs e)
{ if(mark)//如果鼠标左键已按下
{ Graphics g=this.CreateGraphics();//得到窗体的使用的Graphics类对象
Pen pen1=new Pen(Color.Black);//黑笔
g.DrawLine(pen1,point.X,point.Y,e.X,e.Y);//画线,注意画线起始点和终点
point.X=e.X;//记录画下一线段的起始点的坐标
point.Y=e.Y;
}
}
private void Form1_MouseUp(object sender, System.Windows.Forms.MouseEventArgs e)
{ mark=false;}//表示鼠标移动不再画线
(3) 运行,在主窗体中拖动鼠标左键可以画线。但最小化后再最大化后,图形消失。现修改上例,使其克服这个缺点。实现的思路是记录每一条曲线的每一条很短线段的坐标。使用ArrayList类对象记录曲线以及曲线中的点,请注意ArrayList类使用方法。
(4) 在定义主窗体的Form1类中增加私有变量:
private ArrayList Point_List;//用来记录1条曲线的所有点。
private ArrayList Line_List;//用来记录每条曲线,既Point_List对象。
(5) 在Form1类构造函数中增加语句:Line_List=new ArrayList();
(6) 修改主窗体事件OnMouseDown,OnMouseUp,OnMouseMove事件处理函数如下:
private void Form1_MouseDown(object sender,//阴影部分为修改的内容
System.Windows.Forms.MouseEventArgs e)
{ if(e.Button==MouseButtons.Left)
{ Point_List=new ArrayList();//建立数组,记录1条曲线的所有点
point.X=e.X;
point.Y=e.Y;
mark=true;
Point_List.Add(point);//记录曲线起点的坐标
}
}
private void Form1_MouseMove(object sender,
System.Windows.Forms.MouseEventArgs e)
{ if(mark)
{ Graphics g=this.CreateGraphics();
Pen pen1=new Pen(Color.Black);
g.DrawLine(pen1,point.X,point.Y,e.X,e.Y);
point.X=e.X;
point.Y=e.Y;
Point_List.Add(point);//记录曲线中其它点的坐标
}
}
private void Form1_MouseUp(object sender,System.Windows.Forms.MouseEventArgs e)
{ mark=false;
Line_List.Add(Point_List);//记录此条线,注意参数是Point_List
}
(7) 增加主窗体的Paint事件处理函数如下,该函数重画记录的所有曲线。
private void Form1_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{ Graphics g=e.Graphics;
Pen pen1=new Pen(Color.Black);
Point p1,p2;
foreach(ArrayList l in Line_List)//取出每条线
{ for(int k=0;k<(l.Count-1);k++)//重画每条线的点
{ p1=(Point)l[k];
p2=(Point)l[k+1];
g.DrawLine(pen1,p1,p2);
}
}
}
(8) 运行,在Form窗体拖动鼠标可以画线。最小化后再最大化后,图形不消失。
5.7.2 ArrayList类
ArrayList类是容量可以动态增加的数组,其元素可以是任意类型。和其它数组一样,ArrayList类可以使用对象名[索引号]引用其元素,索引号也从零开始。前边已多次使用此类,例如,控件ListBox和ComboBox的属性Items都是引用ArrayList类对象,5.7.1节中的例子用ArrayList类对象记录曲线的各个点和各个曲线。其常用的属性及方法如下:
l 属性Count:ArrayList中实际包含的元素数。
l 方法Add:将参数指定的对象添加到ArrayList对象的结尾处。
l 方法Clear:从ArrayList中移除所有元素。
l 方法Contains:bool类型,确定参数指定的元素是否在ArrayList中。
l 方法IndexOf:int类型,顺序查找和参数指定对象相同的第一个元素的索引。
l 方法Insert:插入数据,第1个参数为插入的位置(索引号),第2个参数为插入的对象。
l 方法LastIndexOf:顺序查找和参数指定对象相同的最后一个元素的索引。
l 方法RemoveAt:移除参数指定索引处的元素。
l 方法Sort:对整个ArrayList中的元素进行排序。
5.7.3 DrawEllipse方法画椭圆(圆)及键盘事件
画椭圆的两个方法如下,功能是画指定矩形的内切椭圆,如为正方形则画圆。
l void DrawEllipse(Pen pen,int x,int y,int width,int height);
其中pen为画笔,画外轮廓线,(x,y)为指定矩形的左上角坐标,width为指定矩形的宽,height为指定矩形的高。
l void DrawEllipse(Pen pen,Rectangle rect);
其中pen为画笔,画外轮廓线,rect为指定矩形结构对象。
例子e5_7_3A:画椭圆。为主窗体Paint事件增加事件处理函数如下:
private void Form1_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{ Graphics g=this.CreateGraphics();
Pen pen1=new Pen(Color.Red);
g.DrawEllipse(pen1,10,10,200,100);
Rectangle rect=new Rectangle(20,20,100,100);
g.DrawEllipse(pen1,rect);
}
例子e5_7_3B:用键盘的四个箭头键移动窗体中的圆。移动圆,实际是先把前边画的圆擦掉,在新的位置重新画圆。如要擦除圆,可以用窗体背景色作为笔和刷子的颜色,在要擦除的圆的位置重画和填充圆。注意键盘事件处理函数的使用。具体实现步骤如下:
(1) 新建项目。在Form1类中增加变量:int x,y,记录定义圆位置的矩形左上角的坐标。
(2) 在Form1类中增加一个方法,该方法按照参数指定颜色画圆,方法定义如下:
void DrawCir(Color color)//参数是画圆的笔和刷子的颜色
{ Graphics g=this.CreateGraphics();
Pen pen1=new Pen(color);
SolidBrush brush1=new SolidBrush(color);
g.DrawEllipse(pen1,x,y,100,100);
g.FillEllipse(brush1,x,y,100,100);
}
(3) 为主窗体Paint事件增加事件处理函数如下:
private void Form1_Paint(object sender,System.Windows.Forms.PaintEventArgs e)
{ DrawCir(Color.Red);}
(4) 为主窗体KeyDown事件增加事件函数如下:(注意不要使用KeyPress事件,其事件处理函数的第2个参数e的e.KeyChar是按下键的ASCII值,但很多键无ASCII值。)
private void Form1_KeyDown(object sender,System.Windows.Forms.KeyEventArgs e)
{ switch (e.KeyCode)//e.KeyCode是键盘每个键的编号
{ case Keys.Left://Keys.Left是左箭头键编号
DrawCir(this.BackColor);//用主窗体的背景色画圆,即擦除圆
x=x-10;//圆左移
DrawCir(Color.Red);//在新的位置用红色画圆,效果是圆左移
break;
case Keys.Right://圆右移
DrawCir(this.BackColor);
x+=10;
DrawCir(Color.Red);
break;
case Keys.Down://圆下移
DrawCir(this.BackColor);
y+=10;
DrawCir(Color.Red);
break;
case Keys.Up://圆上移
DrawCir(this.BackColor);
y=y-10;
DrawCir(Color.Red);
break;
}
}
(5) 运行,可以用4个箭头键移动红色圆。
键盘KeyDown事件处理函数的第2个参数e的e.KeyCode是被按下键的编号,常用键的编号如下:数字键0-9编号为Keys.D0-Keys.D9;字母键A-Z为Keys.A-Keys.Z;F0-F12键表示为Keys.F0-Keys.F12等。
键盘KeyPress事件处理函数的第2个参数e的e.KeyChar表示被按下键的ACSII值,例如可用如下语句if(e.KeyChar==(char)13)判断是否按了回车键。
5.7.4 DrawRectangle方法画矩形
绘制1个和多个矩形(正方形)的方法定义如下:
l void DrawRectangle(Pen pen,int x,int y,int width,int height);
其中pen为画笔,画外轮廓线,(x,y)为矩形的左上角坐标,width为指定矩形的宽,height为指定矩形的高。
l void DrawRectangle(Pen pen,Rectangle rect);
其中pen为画笔,画外轮廓线,rect为矩形结构对象。
l public void DrawRectangles(Pen pen,Rectangle[] rects);
绘制一系列由Rectangle结构数组指定的矩形。
例子e5_7_3:画矩形。为主窗体Paint事件增加事件处理函数如下:
private void Form1_Paint(object sender,System.Windows.Forms.PaintEventArgs e)
{ Graphics g=this.CreateGraphics();
Pen pen1=new Pen(Color.Red);
g.DrawRectangle(pen1,10,10,200,100);
Rectangle rect=new Rectangle(20,20,100,100);
g.DrawRectangle(pen1,rect);
}
5.7.5 DrawArc方法绘制圆弧
DrawArc方法绘制指定矩形的内切椭圆(圆)中的一段圆弧,方法定义如下:
void DrawArc(Pen pen,int x,int y,int width,int height,int StartAngle,int EndAngle);
其中pen为画笔,画外轮廓线,(x,y)为矩形的左上角坐标,width为指定矩形的宽,height为指定矩形的高。StartAngle为圆弧的起始角度,EndAngle为圆弧的结束角度,单位为度。指定矩形的中心点做矩形宽和高的的垂线作为x,y轴,中心点为圆点。圆点右侧x轴为0度,顺时针旋转为正角度,逆时针旋转为负角度。
例子e5_7_4:画圆弧。为主窗体Paint事件增加事件处理函数如下:
private void Form1_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{ Graphics g=this.CreateGraphics();
Pen pen1=new Pen(Color.Red);
g.DrawArc(pen1,10,10,200,100,0,30);
}
5.7.6 DrawPie方法绘制饼图
DrawPie方法绘制指定矩形的内切椭圆(圆)中的一段圆弧,并且用指定矩形的中心点连接开始点和结束点,这个图形叫做饼图,方法定义如下:
void DrawPie(Pen pen,int x,int y,int width,int height,int StartAngle,int EndAngle);
方法参数和DrawArc方法参数相同。
例子e5_7_5:画饼图。为主窗体Paint事件增加事件处理函数如下:
private void Form1_Paint(object sender,System.Windows.Forms.PaintEventArgs e)
{ Graphics g=this.CreateGraphics();
Pen pen1=new Pen(Color.Red);
g.DrawPie(pen1,10,10,200,100,0,30);
}
5.7.7 DrawBezier方法绘制Bezier曲线
DrawBezier方法绘制一条Bezier(贝塞尔)曲线。两个画线函数定义如下:
l Void DrawBezier(Pen pen,float x1,float y1,
float x2,float y2,float x3,float y3,float x4,float y4);
其中pen是画笔对象,画轮廓线,(x1,y1)是起始点,(x2,y2)是第一控制点,(x3,y3)是第二控制点,(x4,y4)是结束点。绘制一条Bezier曲线必须定义4个点
l Void DrawBezier(Pen pen,Point p1,Point p2,Point p3,Point P4);
pen是画笔对象,画轮廓线,p1是起始点,p2和p3是第一、第二控制点,p4是结束点。
例子e5_7_6:画Bezier曲线。为主窗体Paint事件增加事件处理函数如下:
private void Form1_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{ Graphics g=this.CreateGraphics();
Pen pen1=new Pen(Color.Red);
g.DrawBezier(pen1,10,10,200,100,50,60,100,200);
}
5.7.8 DrawPolygon方法绘制多边形
该方法绘制一个多边形,使用点结构数组定义多边形的顶点。画线函数定义如下:
l void DrawPolygon(Pen pen,Point[] points);//参数2也可为PointF[],点坐标是小数
例子e5_7_7:画一个多边形如下图,为主窗体Paint事件增加事件处理函数如下:
private void Form1_Paint(object sender,System.Windows.Forms.PaintEventArgs e)
{ Graphics g=this.CreateGraphics();
Pen pen1=new Pen(Color.Red);
Point[] p1=new Point[]
{ new Point(10,10),new Point(60,40),
new Point(100,80),new Point(60,100)
};
g.DrawPolygon(pen1,p1);
}
5.7.9 DrawClosedCurve方法绘制闭合基数样条
DrawClosedCurve方法用来绘制经过Point结构数组中每个点的闭合基数样条。基数样条是一连串单独的曲线,这些曲线连接起来形成一条较大的曲线。样条由点的数组指定,并通过该数组中的每一个点。基数样条平滑地通过数组中的每一个点,请比较一下本节的图形和上节图形的区别。如果最后一个点不匹配第一个点,则在最后一个点和第一个点之间添加一条附加曲线段以使该图闭合,点Point结构数组必须至少包含四个元素,此方法使用默认张力0.5。有4个画线函数,常用的一个画线函数定义如下:
l void DrawClosedCurve(Pen pen,Point[] points);//参数2也可为PointF[]
例子e5_7_9:使用DrawClosedCurve方法,绘制有4个元素的Point结构数组定义的闭合基数样条闭合曲线如下图,为主窗体Paint事件增加事件处理函数如下:
private void Form1_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{ Graphics g=this.CreateGraphics();
Pen pen1=new Pen(Color.Red);
Point[] p1=new Point[]
{ new Point(10,10),new Point(60,40),
new Point(100,80),new Point(60,100)
};
g.DrawClosedCurve(pen1,p1);
}
5.7.10 DrawCurve方法绘制不闭合基数样条
用DrawCurve方法和DrawClosedCurve方法一样,用来绘制经过Point结构数组中每个点的基数样条,但最后两个点之间不连线。常用的一个画线函数定义如下:
l void DrawCurve(Pen pen,Point[] points);//参数2也可为PointF[],点坐标是小数
例子5_7_10:使用DrawCurve方法,绘制有4个元素的Point结构数组定义的闭合基数样条闭合曲线如下图,为主窗体Paint事件增加事件处理函数如下:
private void Form1_Paint(object sender,System.Windows.Forms.PaintEventArgs e)
{ Graphics g=this.CreateGraphics();
Pen pen1=new Pen(Color.Red,3);
Point[] p1=new Point[]
{ new Point(10,10),new Point(60,40),
new Point(100,80),new Point(60,100)
};
g.DrawCurve(pen1,p1);
}
5.7.11 DrawPath方法和GraphicsPath类
用DrawPath方法可以绘制多个曲线,方法的第2个参数GraphicsPath类对象path定义要绘制的多条曲线或封闭曲线(例如矩形)。DrawPath方法定义如下:
void DrawPath(Pen pen,GraphicsPath path);
例子5_7_11:用DrawPath方法画一个矩形和其内切椭圆如下图,在Form1.cs文件的头部增加语句:using System.Drawing.Drawing2D,主窗体Paint事件处理函数如下:
private void Form1_Paint(object sender,System.Windows.Forms.PaintEventArgs e)
{ Rectangle myEllipse=new Rectangle(20,20,100,50);
GraphicsPath myPath=new GraphicsPath();//建立GraphicsPath类空对象
myPath.AddEllipse(myEllipse);//追加一椭圆路径
myPath.AddRectangle(myEllipse);//追加一矩形路径
Pen myPen=new Pen(Color.Black,2);
//画这条曲线,即椭圆和矩形。
e.Graphics.DrawPath(myPen,myPath);
}
GraphicsPath类可以表示一条复杂曲线的绘制路径,这条复杂曲线中可以包括若干相互连接的直线、曲线(例如贝塞尔曲线)以及封闭图形(例如矩形)等。可以使用DrawPath方法绘制GraphicsPath类定义的曲线的轮廓、使用FillPath方法填充GraphicsPath类定义的封闭曲线,创建GraphicsPath类定义的封闭曲线区域。常用属性和方法定义如下:
l 属性PathPoints:点结构数组,表示绘制直线、曲线和封闭图形的路径所使用的点。
l 属性PathTypes:字节数组,和PathPoints点结构数组长度相同,数组元素是曲线或图形类型,每个元素指定PathPoints点结构数组中索引号相同的点是何种曲线或图形,例如,4个点可以定义一条贝塞尔曲线,参见5.6.5节。
l 属性PointCount:获取 PathPoints 或 PathTypes 数组中的元素数。
l 构造函数GraphicsPath();//建立空对象
l 构造函数public GraphicsPath(Point[] pts,byte[] types);
参数pts和types意义和属性PathPoints、PathTypes相同。参见5.6.5节。
l 方法IsVisible:参数指定的点或矩形是否包含在此GraphicsPath对象内。
l 方法:void AddArc(Rectangle rect,float startAngle,float sweepAngle);
在代表要描绘图形的GraphicsPath类对象中追加一段椭圆弧路径。
l 方法:void AddEllipse(Rectangle rect),追加一椭圆(圆)路径
l 方法:void AddLine(Point pt1,Point pt2),追加一线段路径
l 方法:void AddRectangle(Rectangle rect),追加一矩形路径
GraphicsPath类还有其它增加曲线路径的方法,例如:AddBezier、AddBeziers、AddClosedCurve、AddCurve、AddLines、AddPath、AddRectangles、AddPolygon、AddString、AddPie等方法,请读者用MSDN或VS.Net帮助查看。
5.7.12DrawString方法写字符串
DrawString方法在指定位置用指定的Brush和Font对象绘制指定的文本字符串。有6个重载方法,常用的一个是:
public void DrawString(string s,//s是为要显示的字符串
Font font,//显示的字符串使用的字体
Brush brush,//用刷子写字符串
PointF point);//显示的字符串左上角的坐标
最后一个参数也可以是RectangleF对象,表示字符串要显示在此矩形内。还可以再增加一个参数,即第5个参数,StringFormat对象,它指定所绘制文本的格式化属性(如行距和对齐方式)。在打印和打印预览一节已使用过这个方法。
例子e5_7_12:用DrawString方法显示字符串,主窗体Paint事件处理函数如下:
private void Form1_Paint(object sender,System.Windows.Forms.PaintEventArgs e)
{ String drawString="Sample Text";//要显示的字符串
Font drawFont=new Font("Arial",16);//显示的字符串使用的字体
SolidBrush drawBrush=new SolidBrush(Color.Black);//写字符串用的刷子
PointF drawPoint=new PointF(20.0F,20.0F);//显示的字符串左上角的坐标
e.Graphics.DrawString(drawString,drawFont,drawBrush,drawPoint);
}
5.7.13DrawImage和DrawIcon方法
DrawImage方法用来在指定的位置绘制指定的Image对象。Graphics类中有多个DrawImage重载方法,最简单的是以下方法,注意第2、3个方法可以对图像进行缩放。
l public void DrawImage(Image image,Point point);
在指定的位置使用原始物理大小绘制指定的Image对象。参数1为要绘制的Image对象,参数2表示所绘制图像的左上角在窗体中的位置。
l public void DrawImage(Image image,Point[] destPoints);
在指定位置并且按指定形状和大小绘制指定的Image对象。参数1为要绘制的Image对象,参数2表示有3个元素的Point结构数组,三个点定义一个平行四边形。缩放image参数引用的图像到合适的大小,以便在此平行四边形内显示。参数2也可以是矩形结构。
l public void DrawImage(Image image,//要绘制的Image对象
Rectangle destRect,//指定所绘制图像的位置和大小,图像进行缩放以适合该矩形
Rectangle srcRect,//指定image对象中要绘制的部分
GraphicsUnit srcUnit);//枚举的成员,指定srcRect参数所用的度量单位
例子e5_7_13A:在指定位置绘制Image对象指定部分。
private void Form1_Paint(object sender,System.Windows.Forms.PaintEventArgs e)
{ Image newImage=Image.FromFile("d:\\CSARP\\1.jpg");//建立要绘制的Image图像
Rectangle destRect=new Rectangle(10,10,150,150);//显示图像的位置
Rectangle srcRect=new Rectangle(50,50,150,150);//显示图像那一部分
GraphicsUnit units=GraphicsUnit.Pixel;//源矩形的度量单位设置为像素
e.Graphics.DrawImage(newImage,destRect,srcRect,units);//在主窗体显示
}//如果把显示图像的位置变宽,看一下效果,为什么?其它重载方法可用帮助查看。
l public void DrawIcon(Icon icon,Rectangle targetRect);
本方法在Rectangle结构指定的区域内绘制指定的Icon对象引用的图标。
例子e5_7_13B:在指定位置画图标。
private void Form1_Paint(object sender,System.Windows.Forms.PaintEventArgs e)
{ Icon newIcon=new Icon("d:\\CSARP\\TASKS.ICO");
Rectangle rect=new Rectangle(100,100,200,200);
e.Graphics.DrawIcon(newIcon,rect);
}
5.7.14FillEllipse方法填充内切椭圆(圆)
该方法用画刷填充指定矩形的内切椭圆(圆)。两个填充函数的定义如下:
l void FillEllipse(Brush brush,int x,int y,int width,int height);
brush为填充矩形的画刷,(x,y)为指定矩形的左上角坐标,width为指定矩形的宽,height为指定矩形的高。
l void DrawEllipse(Brush brush,Rectangle rect);//rect为指定矩形结构对象
例子e5_7_14:用指定画刷来填充指定矩形的内切椭圆。
private void Form1_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{ Graphics g=this.CreateGraphics();
SolidBrush brush=new SolidBrush(Color.Blue);
g.FillEllipse(brush,10,10,200,100);
Rectangle rect=new Rectangle(120,120,100,100);
g.FillEllipse(brush,rect);
}
5.7.15FillRectangle方法填充矩形
FillRectangle方法用指定画刷来填充指定矩形。两个填充函数定义如下:
l void FillRectangle(Brush brush,int x,int y,int width,int height);
brush为填充矩形的画刷,(x,y)为矩形的左上角坐标,width为指定矩形的宽,height为指定矩形的高。
l void FillRectangle(Brush brush,Rectangle rect);//rect为矩形结构对象
例子e5_7_15:用指定画刷来填充指定矩形。
private void Form1_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{ Graphics g=this.CreateGraphics();
SolidBrush brush=new SolidBrush(Color.Blue);
g.FillRectangle(brush,10,10,200,100);
Rectangle rect=new Rectangle(120,120,100,100);
g.FillRectangle(brush,rect);
}
5.7.16FillPie方法填充饼图
FillPie方法用指定画刷来填充指定饼图。函数定义如下:
void FillPie(Brush brush,int x,int y,int width,
int height,int StartAngle,int EndAngle);
brush为填充矩形的画刷,方法其它参数和DrawArc方法参数相同。
例子e5_7_16:用指定画刷来填充指定饼图。
private void Form1_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{ Graphics g=this.CreateGraphics();
SolidBrush brush=new SolidBrush(Color.Blue);
g.FillPie(brush,10,10,200,100,0,30);
}
5.7.17FillRegion方法和Region类
FillRegion方法用刷子填充区域Region类对象内部。Region类对象由指定闭合曲线(例如矩形)或路径构成。如果区域不闭合,则在最后一个点和第一个点之间添加一条额外的线段来将其闭合。方法定义如下:
public void FillRegion(Brush brush,Region region);
第1个参数是填充使用的刷子,第2个参数是指定的区域。
例子e5_7_17A:用纯蓝色刷子,使用FillRegion方法填充一个矩形区域。
private void Form1_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{ SolidBrush blueBrush=new SolidBrush(Color.Blue);
Rectangle fillRect=new Rectangle(10,10,100,100);
Region fillRegion=new Region(fillRect);//创建区域类对象,代表矩形内部区域
e.Graphics.FillRegion(blueBrush,fillRegion);
}
区域是输出设备显示区域的一部分。区域可以是简单的(例如单个矩形)或复杂的(例如多边形或闭合曲线的组合)。下图中的左数第1图显示了两个区域:一个利用矩形构造,另一个利用路径构造。可以通过合并现有的区域来创建复杂区域。Region类提供了以下合并区域的方法:Intersect、Union、Xor、Exclude和Complement。两个区域的交集是同时属于两个区域的所有点的集合,方法Intersect可以得到两个Region类对象的交集。并集是多个区域的所有点的集合,方法Union可以得到两个Region类对象的并集。方法Xor可以得到两个Region类对象的并集减去这两者的交集,即下图中的左数第4图显示的黑色区域。方法Exclude和Complement可以得到1个Region类对象和参数指定的Region类对象的不相交的部分,即下图中的左数第5图显示区域。
Region类常用的方法如下:
l 构造函数Region:构造函数可以没有参数,即创建一个空区域。也可以有一个参数,参数是GraphicsPath、Rectangle、RectangleF等类型定义的封闭曲线,由此生成一个代表参数指定封闭曲线内部的区域。例如:
Rectangle aRect=new Rectangle(10,10,60,60);
Region aRegion=new Region(aRect);//创建区域类对象,代表矩形内部区域
l 方法Exclude和Complement:得到1个Region类对象和参数指定对象的不相交的部分。参数可以是GraphicsPath、Rectangle、RectangleF和Region类型。例如:
Rectangle bRect=new Rectangle(10,10,50,50);
aRegion.Exclude(bRect);//使aRegion代表两个矩形不相交的部分
l 方法Equals:比较2个区域是否相等。参数1是要比较的区域,参数2是要绘制表面的Graphics对象。
l 方法Intersect:可以得到两个Region类对象的交集。参数可以是GraphicsPath、Rectangle、RectangleF和Region类型。例如:aRegion.Intersect(bRect);
l 方法IsEmpty(Graphics g):测试是否为空区域。
l 方法IsVisible:测试参数指定的点或矩形是否在区域中。例如:
bool b1=aRegion.IsVisible(bRect);//应返回true
l 方法Union:可以得到两个Region类对象的并集。例如:aRegion.Union(bRect);
l 方法Xor:可以得到两个Region类对象的并集减去这两者的交集。
例子e5_7_17B:将一个正方形和一个圆的相交部分填充为蓝色。在Form1.cs文件的头部增加语句:using System.Drawing.Drawing2D,主窗体Paint事件处理函数如下:
private void Form1_Paint(object sender,System.Windows.Forms.PaintEventArgs e)
{ Graphics g=e.Graphics;
Rectangle regionRect=new Rectangle(10,10,50,50);//定义第1个正方形
Pen pen1=new Pen(Color.Black);
g.DrawRectangle(pen1,regionRect);//绘制第1个正方形,边界为黑色
RectangleF unionRect=new RectangleF(25,25,50,50);//定义第2个正方形
pen1.Color=Color.Red;
g.DrawEllipse(pen1,unionRect);//绘制第2个正方形的内切圆,边界为红色
GraphicsPath myPath=new GraphicsPath();//建立GraphicsPath()类对象
myPath.AddEllipse(unionRect);//myPath是第2个正方形的内切圆的路径
Region myRegion=new Region(regionRect);//建立表示第1个正方形的区域
myRegion.Intersect(myPath);//得到两个区域的交集
SolidBrush myBrush=new SolidBrush(Color.Blue);
e.Graphics.FillRegion(myBrush,myRegion);//填充交集区域
}
运行效果如右图。除了以上介绍的填充方法,还有如下填充方法:FillClosedCurve方法、FillPath方法、FillPolygon方法等,请用帮助查看。
5.8 Matrix类和图形的平移、变形、旋转
本节介绍使用Matrix类实现图形的平移、变形、旋转。
5.8.1 Matrix类
Matrix类封装了表示几何变形的3行3列仿射矩阵,可以记录图形的平移、变形、旋转等操作。主要包括如下方法:
l 构造函数Matrix():创建一个空Matrix类对象。见例子e5_8_1中第1条语句。
l 方法Rotate:在Matrix类对象中增加相对于原点顺时针旋转指定角度的操作。参数指定旋转角度。见例子e5_8_1中第2条语句。
l 方法RotateAt:在Matrix类对象中增加相对于指定点顺时针旋转指定角度的操作。参数1指定旋转角度。参数2指定相应的点。
l 方法Scale:在X轴或Y轴方向对图形放大或缩小。参数1指定在X轴方向缩放的值,参数2指定在Y轴方向缩放的值。见例子e5_8_1中第3条语句。
l 方法Translate:使图形在X轴和Y轴方向移动。参数1指定在X轴方向移动的值,参数2指定在Y轴方向移动的值。见例子e5_8_1中第4条语句。
例子e5_8_1:下面的示例创建了记录复合变换的Matrix类对象,该变换先旋转30度,然后在y方向上放大2倍,最后在x方向平移5个单位。注意变换得顺序非常重要。一般说来,先旋转、再缩放、然后平移,与先缩放、再旋转、然后平移是不同的。
Matrix myMatrix=new Matrix();//生成Matrix类空对象,将记录复合变换
myMatrix.Rotate(30);//旋转30度
myMatrix.Scale(1,2,MatrixOrder.Append);//增加在y方向上放大2倍变换
myMatrix.Translate(5,0,MatrixOrder.Append);//增加在x方向平移5个单位变换
5.8.2 图形的平移、变形、旋转
GraphicsPath类的方法void Transform(Matrix matrix)可以缩放、转换或旋转GraphicsPath对象所代表的图形,参数Matrix类对象matrix表示需要的变换。
例子5_8_2:以下代码执行如下操作:创建GraphicsPath类对象myPath,并向其添加一个椭圆。将myPath代表的椭圆绘制到屏幕。创建一个Matrix类对象,并增加在X轴方向上移动100个单位操作。myPath执行变换并将已变换的图形绘制到屏幕。观察一下变换前后的不同,注意,初始椭圆是黑色的,而变换后的椭圆是红色的。在Form1.cs文件的头部增加语句:using System.Drawing.Drawing2D,主窗体Paint事件处理函数如下:
private void Form1_Paint(object sender,System.Windows.Forms.PaintEventArgs e)
{ GraphicsPath myPath=new GraphicsPath();//创建一个GraphicsPath类对象
myPath.AddEllipse(0,0,50,70);//添加一个椭圆,myPath记录了椭圆路径
e.Graphics.DrawPath(Pens.Black,myPath);//用黑笔画出这个椭圆
Matrix translateMatrix=new Matrix();//创建一个Matrix类对象
translateMatrix.Translate(25,0);//增加在X轴方向上移动25个单位变换
//根据Matrix类对象修改myPath代表的椭圆路径
myPath.Transform(translateMatrix);
//用红笔按新路径画这个椭圆
e.Graphics.DrawPath(new Pen(Color.Red,2),myPath);
}
运行效果如右图。请读者实现变形、旋转。
5.8.3 仿射矩阵
m×n矩阵是以m行和n列排列的一组数字,例如一个3×3矩阵记为下图形式,也可简记为:[a33]。
两个行、列相同的矩阵可以相加,例如:[a33]+[b33]=[c33],矩阵相加运算的规则是:ci j=ai j+bi j,i和j为常量,即相对应位置的项相加。如果有矩阵[am n]和[bn k],[am n]矩阵的列数等于[bn k]矩阵的行数,两个矩阵可以相乘,记为:[am n]*[bn k]=[cm k],矩阵相乘的运算的规则是:ci j=∑(ai t+bt j),其中,i和j为常量,t为变量,初始值为1,最大值为n。
如果将平面中的点视为1×2矩阵,则可通过将该点乘以2×2变换矩阵来变形该点。下图是点(2,1)在X轴按比例3放大,Y轴不变。
下图表示点(2,1)旋转了90度。
下图表示点(2,1)以x轴为对称轴的新点。
假定要从点(2,1)开始,将其旋转90度,在x方向将其平移3个单位,在y方向将其平移4个单位。可通过先使用矩阵乘法再使用矩阵加法来完成此操作,如下图。
如果用矩阵[2 1 1]代表点(2,1),使用一个3×3变换矩阵,可以用一个矩阵乘法代替以上的两个矩阵运算,见下图:
注意运算结果的矩阵[2 6 1]代表点(2,6),即点(2,1)映射到了点(2,6)。这个3×3矩阵叫作仿射矩阵,Matrix类中用这个仿射矩阵记录增加的各种变换操作。它和前边的两个2×2矩阵的关系如下图,其中第三列固定为0、0、1。
Matrix类增加了一些方法处理这个仿射矩阵,主要包括:逆转方法Invert、相乘方法Multiply、重置为单位矩阵方法Reset等。
5.9 图形文件格式
在磁盘中存储图形和图像的文件格式有多种。GDI+支持以下图形和图像文件格式。
l 位图文件(扩展名为.bmp):
位图文件是Windows使用的一种标准格式,用于存储设备无关和应用程序无关的图像。BMP文件通常不压缩,因此不太适合Internet传输。
l 可交换图像文件格式(扩展名为.gif):
GIF是一种用于在Web页中显示图像的通用格式。GIF文件是压缩的,但是在压缩过程中没有信息丢失,解压缩的图像与原始图像完全一样。GIF文件中的颜色可以被指定为透明,例如指定GIF图像背景色为透明,则图像将具有显示它的任何Web页的背景色。在单个文件中存储一系列GIF图像可以形成一个动画GIF。GIF文件每个像素颜色最多用8位表示,所以它们只限于使用256种颜色。
l JPG文件(扩展名为.jpg):
JPEG是联合摄影专家组提出的一种适应于自然景观(如扫描的照片)的压缩方案。一些信息会在压缩过程中丢失,但是这些丢失人眼是察觉不到的。JPEG文件每像素颜色用24位表示,因此能够显示超过16,000,000种颜色。JPEG文件不支持透明或动画。JPEG图像中的压缩级别是可以控制的,但是较高的压缩级别(较小的文件)会导致丢失更多的信息。对于一幅以20:1压缩比生成的图像,人眼难以把它和原始图像区别开来。JPEG是一种压缩方案,不是一种文件格式。"JPEG文件交换格式(JFIF)"是一种文件格式,常用于存储和传输根据JPEG方案压缩的图像。JFIF文件使用.jpg扩展名。
l 可移植网络图形(扩展名为.PNG)
PNG格式不但保留了许多GIF格式的优点,还提供了超出GIF的功能。像GIF文件一样,PNG文件在压缩时也不损失信息。PNG文件能以每像素8、24或48位来存储颜色,并以每像素1、2、4、8或16位来存储灰度。相比之下,GIF文件只能使用每像素1、2、4或8位。PNG文件还可为每个像素存储一个透明度alpha值,该值指定了该像素颜色与背景颜色混合的程度。PNG优于GIF之处在于它能够逐渐显示一幅图像,也就是说,当图像通过网络传送,显示图像将越来越接近原图像。PNG文件可包含伽玛校正和颜色校正信息,以便图像可在各种各样的显示设备上精确地呈现。
l 图元文件(扩展名为.emf):
GDI+提供Metafile类,以便能够记录和显示图元文件。图元图形,也称为矢量图形,是一种记录一系列绘图命令和设置的图形。Metafile对象记录的命令和设置可以存储在内存中或保存到文件或流中。下面示例在主窗体显示了一个图元文件所代表的图形。
Graphics g=this.CreateGraphics();
Metafile myMetafile=new Metafile("SampleMetafile.emf");
myGraphics.DrawImage(myMetafile,10,10);//图形左上角的位置是(10,10)
l 支持的文件格式还有:图标文件(.ico)、.EXIF、.TIFF、.ICON、.WMF等
5.10 图形框PictureBox控件
PictureBox控件常用于图形设计和图像处理程序,又称为图形框,该控件可显示和处理的图像文件格式有:位图文件(.bmp)、图标文件(.ico)、GIF文件(.gif)和JPG文件(.jpg)。该控件可以自己响应Paint事件,Paint事件处理函数用属性Image引用的位图对象重画图像。其常用的属性、事件和方法如下:
l 属性Image:指定要显示的图像,一般为Bitmap类对象。
l 属性SizeMode:指定如何显示图像,默认为Normal,图形框和要显示的图像左上角重合,只显示图形框相同大小部分,其余不显示;为CentreImage,图像和图形框中心点重合,图像四周超出图形框部分不显示;为StretchImage,缩放图像使之适合图片框。
l 方法CreateGraphics():得到图形框所使用的Graphics类对象。
l 方法Invalidate():要求控件对参数指定区域重画,如无参数,为整个区域。
l 方法Update():方法Invalidate()并不能使控件立即重画指定区域,只有使用Update()方法才能立即重画指定区域。使用见5.11.6节中的鼠标移动事件处理函数。
例子e5_10:使用PictureBox控件显示图像
(1)新建项目。放PictureBox控件到窗体。属性Name=pictureBox1。可以在设计阶段修改属性Image为指定图形文件,设定初始显示的图像。
(2)放OpenFileDialog控件到窗体。属性Name=openFileDialog1。
(3)放Button控件到窗体。属性Name=button1。button1控件单击事件处理函数如下:
private void button1_Click(object sender, System.EventArgs e)
{ if(openFileDialog1.ShowDialog()==DialogResult.OK)
{ Bitmap p1=new Bitmap(openFileDialog1.FileName);//Bitmap类见下节
pictureBox1.Image=p1;
}
}
5.11 Bitmap类
System.Drawing命名空间有一个类Image,用来处理图像。Image类的派生类Bitmap类封装了GDI+中的位图,可以处理由像素数据定义的图像。Image类的派生类metafile类处理图元图形,metafile类用记录绘图命令和设置的方法来存储图像。
5.11.1 Bitmap类支持的图像类型
使用Bitmap类可以显示和处理多种图像文件,可处理的文件类型及文件扩展名如下:扩展名为.bmp的位图文件、扩展名为.ico的图标文件、扩展名为.gif的GIF文件、扩展名为.jpg的JPG文件。当使用构造函数Bitmap(string FileName)建立Bitmap类对象时,如果文件是以上类型,将自动转换为位图格式存到Bitmap类对象中。可使用Bitmap类方法Save(string FileName,ImageFormat imageFormat)把Bitmap类对象中的位图存到文件中,其中第1个参数是选定的文件名,第2个参数是指定文件存为那种类型,可以是如下类型:System.Drawing.Imaging.ImageFormat.bmp(或.ico、.gif、.jpg)。
5.11.2 Bitmap类的方法
l 方法SetPixel():画点方法,前2个参数是指定点的位置,第3个参数是颜色值。
l 方法GetPixle():得到指定点的颜色,2个参数是指定点的位置,返回颜色值。
l 有多个构造函数例如:new Bitmap("图像文件名"),new Bitmap(宽,高)等。
l 方法Save():第1个参数是文件名,第2个参数是指定文件存为那种类型,可以是如下类型:System.Drawing.Imaging.ImageFormat.bmp(或.ico、.gif、.jpg)。
l 方法Dispose():释放位图对象
l 方法Clone():参数1是矩形结构,表示克隆区域。参数2表示单位。见5.12.2节。
5.11.3 SetPixel方法画点
例子e5_11_3:用SetPixel方法画点,GetPixle方法得到指定点的颜色。放Button和PictureBox控件到主窗体。增加Button控件单击事件处理函数如下:
private void button1_Click(object sender,System.EventArgs e)
{ pictureBox1.Width=720;//设定pictureBox1的宽和高
pictureBox1.Height=110;
Bitmap bits=new Bitmap(720,110);//建立位图对象,宽=720,高=110
int x,y;
for(x=0;x<720;x++)//画正弦曲线
{ y=(int)(50+50*Math.Sin((3.14159/180.0)*x));
bits.SetPixel(x,y,Color.Red);
}
pictureBox1.Image=bits;//位图对象在pictureBox1中显示
Color c1=bits.GetPixel(20,20);
string s="R="+c1.R+",G="+c1.B+",G+"+c1.G;
MessageBox.Show(s);
}
5.11.4 在PictureBox中拖动鼠标画曲线
例子e5_11_4:例子e5_7_1C实现了画任意曲线程序,程序用ArrayList类对象记录绘制的曲线。在例子e5_7_1C程序中增加橡皮功能、图像的拷贝、图像的剪切、图像的粘贴比较困难,也不能和Windows画图程序交换文件。为了实现这些功能,用图形框(PictureBox控件)显示绘制图形。绘制图形必须记录到图形框属性Image引用的位图对象中,图形框显示的图像被破坏需恢复时,图形框自动响应Paint事件,用其属性Image引用的位图对象恢复所绘制的图形。仅将图形绘制在图形框表面,图形框响应Paint事件,绘制的图形将不能被恢复,也就是说,绘制在图形框表面的图形丢失了。具体实现步骤如下:
(1) 新建项目。为Form1类增加4个私有变量:private bool mark=false;
private Point point; private Bitmap bits; private Graphics bitG;
(2) 放PictureBox控件到窗体,修改属性Dock=Fill。
(3) 在构造函数中增加语句:
//建立位图类对象,宽和高为指定值
bits=new Bitmap(pictureBox1.Width,pictureBox1.Height);
bitG=Graphics.FromImage(bits);//得到位图对象的Graphics类的对象
bitG.Clear(Color.White);//用白色清除位图对象中的图像
pictureBox1.Image=bits;//bits记录了pictureBox1显示的图像
(4) 为控件PictureBox事件OnMouseDown,OnMouseUp,OnMouseMove增加事件处理函数:
private void pictureBox1_MouseDown(object sender,//鼠标按下事件处理函数
System.Windows.Forms.MouseEventArgs e)
{ if(e.Button==MouseButtons.Left)//是否是鼠标左键按下
{ point.X=e.X;
point.Y=e.Y;//画线段开始点
mark=true;//鼠标左键按下标识
}
}
private void pictureBox1_MouseMove(object sender,//鼠标移动事件处理函数
System.Windows.Forms.MouseEventArgs e)
{ if(mark)//如果鼠标左键按下
{ Graphics g=pictureBox1.CreateGraphics();
Pen pen1=new Pen(Color.Black);
g.DrawLine(pen1,point.X,point.Y,e.X,e.Y);//图形画在PictureBox表面
bitG.DrawLine(pen1,point.X,point.Y,e.X,e.Y);//图形画在位图对象bits中
point.X=e.X;
point.Y=e.Y;//下次绘制画线段开始点
}
}
private void pictureBox1_MouseUp(object sender,
System.Windows.Forms.MouseEventArgs e)
{ mark=false;
pictureBox1.Image=bits;//保存了所画的图形
}
(5) 运行,在PictureBox控件拖动鼠标可以画线。最小化后再最大化后,图形不消失。
5.11.5 存取位图文件
例子e5_11_5:为上例增加存取位图文件功能。
(6) 把Mainmenu控件放到主窗体中。增加顶级菜单项:文件,属性Name=menuItemFile。为"文件"顶级菜单项的弹出菜单增加菜单项:新建、打开、另存为、退出,属性Name分别为menuItemNew、menuItemOpen、menuItemSaveAs、menuItemExit。
(7) 为主窗体菜单"文件"|"新建"菜单项增加单击事件函数如下:
private void menuItemNew_Click(object sender, System.EventArgs e)
{ bitG.Clear(Color.White);//用白色清空位图对象bitG
pictureBox1.Image=bits;//pictureBox1显示用白色清空位图对象bitG
}
(8) 放OpenFileDialog控件到窗体。菜单"文件"|"打开"菜单项单击事件处理函数如下:
private void menuItemOpen_Click(object sender,System.EventArgs e)
{ if(openFileDialog1.ShowDialog(this)==DialogResult.OK)
{ bits.Dispose();//撤销bitG所引用的对象
bits=new Bitmap(openFileDialog1.FileName);//建立指定文件的新位图对象
bitG=Graphics.FromImage(bits);//得到位图对象使用的Graphics类对象
pictureBox1.Image=bits;
}
}
(9) 放SaveFileDialog控件到窗体。菜单"文件"|"另存为"菜单项单击事件处理函数如下:
private void menuItemSaveAs_Click(object sender,System.EventArgs e)
{ if(saveFileDialog1.ShowDialog(this)==DialogResult.OK)
{ string s=saveFileDialog1.FileName+".bmp";
bits.Save(s,System.Drawing.Imaging.ImageFormat.Bmp);
}
}//也可以存为其它格式,例如:Jpg,Gif等。请读者试一下。
(10) 为主窗体菜单"文件"|"退出"菜单项增加单击事件处理函数如下:
private void menuItemExit_Click(object sender, System.EventArgs e)
{ Close();}
(11) 运行,在PictureBox控件拖动鼠标可以画线。存所画的图形到文件,再重新读出该文件,看是否正常运行。检查Windows画图程序能否打开本程序所存的图形文件。
5.11.6 用拖动鼠标方法画椭圆或圆
Windows画图程序用拖动鼠标方法画椭圆或圆,实现的方法是:以鼠标左键被按下处作为矩形的一个顶点,记为顶点1,该点坐标不改变。拖动鼠标移动到另一位置,以此位置作为矩形另一顶点,记为顶点2,顶点1和顶点2在矩形对角线的两端。绘制由顶点1和顶点2定义的矩形的内切椭圆,以显示要绘制椭圆的位置,这个椭圆的位置随着鼠标的移动而改变。鼠标抬起,以鼠标抬起位置为顶点2,用指定的画笔和画刷绘制由顶点1和顶点2定义的矩形的内切椭圆,作为最终图形。本节程序实现此功能。
如果图形仅绘制在图形框(PictureBox控件)上,而不保存到其属性Image引用的位图对象中,当调用图形框的Invalidate()方法,发出Paint事件,Paint事件处理函数用图形框属性Image引用的位图对象恢复图像,将擦除仅绘制在图形框上的图形。拖动鼠标方法画椭圆或圆显示位置时,仅将椭圆或圆画在PictureBox上,在鼠标拖动显示下一个位置前,用图形框的Invalidate()方法擦除前一位置所画的图形。
例e5_11_6:本例实现拖动鼠标方法画椭圆或圆,实现步骤如下:
(1) 新建项目。为Form1类增加5个变量:Point EndPoint; Point StartPoint;
Bitmap bits; Graphics bitG; bool mark=false;
(2) 放PictureBox控件到子窗体。修改属性Dock=Fill。
(3) 在构造函数中增加语句:
//bits用来保存pictureBox1中位图图像,是pictureBox1属性Image引用的对象
bits=new Bitmap(pictureBox1.Width,pictureBox1.Height);
bitG=Graphics.FromImage(bits);//得到位图对象使用的Graphics类对象
bitG.Clear(Color.White);
pictureBox1.Image=bits;
(4) 在Form1类中增加MakeRectangle方法返回由参数指定的两个点定义的矩形。方法如下:
private Rectangle MakeRectangle(Point p1,Point p2)
{ int top,left,bottom,right;
top=p1.Y<=p2.Y? p1.Y:p2.Y;//计算矩形左上角点的y坐标
left=p1.X<=p2.X? p1.X:p2.X;//计算矩形左上角点的x坐标
bottom=p1.Y>p2.Y? p1.Y:p2.Y;//计算矩形右下角点的y坐标
right=p1.X>p2.X? p1.X:p2.X;//计算矩形右下角点的x坐标
return(new Rectangle(left,top,right,bottom));//返回矩形
}
(5) 为PictureBox事件OnMouseDown、OnMouseUp、OnMouseMove增加事件处理函数如下:
private void pictureBox1_MouseDown(object sender,//鼠标按下事件处理函数
System.Windows.Forms.MouseEventArgs e)
{ if(e.Button==MouseButtons.Left)//如果按下鼠标左键,开始拖动鼠标画图
{ StartPoint.X=e.X;//以鼠标左键被按下处作为矩形的一个顶点
StartPoint.Y=e.Y;//StartPoint记录矩形的这个顶点
EndPoint.X=e.X;//拖动鼠标移动的位置作为矩形另一顶点
EndPoint.Y=e.Y;//EndPoint记录矩形的这个顶点,两个顶点定义一个矩形
mark=true;//开始拖动鼠标画图标记
}
}
private void pictureBox1_MouseMove(object sender,//鼠标移动事件处理函数
System.Windows.Forms.MouseEventArgs e)
{ if(mark)
{ Rectangle r1=MakeRectangle(StartPoint,EndPoint);//计算重画区域
r1.Height+=2;
r1.Width+=2;//区域增大些
pictureBox1.Invalidate(r1);//擦除上次鼠标移动时画的图形,r1为擦除区域
pictureBox1.Update();//立即重画,即擦除
Graphics g=pictureBox1.CreateGraphics();
Pen pen1=new Pen(Color.Black);
EndPoint.X=e.X;
EndPoint.Y=e.Y;
r1=MakeRectangle(StartPoint,EndPoint);//计算椭圆新位置
g.DrawEllipse(pen1,r1);//在新位置画椭圆,显示椭圆绘制的新位置
}
}
private void pictureBox1_MouseUp(object sender,//鼠标抬起事件处理函数
System.Windows.Forms.MouseEventArgs e)
{ Pen pen1=new Pen(Color.Black);
EndPoint.X=e.X;
EndPoint.Y=e.Y;
Rectangle r1=MakeRectangle(StartPoint,EndPoint);
bitG.DrawEllipse(pen1,r1);//最终椭圆画在pictureBox1属性Image引用的对象中
mark=false;
pictureBox1.Image=bits;//显示画椭圆的最终结果
}
(6) 运行,在PictureBox控件中拖动鼠标可以画圆或椭圆。
5.12 图像剪贴板功能
Windows中的许多程序都支持剪贴板功能。通过剪贴板可以完成显示数据或图形图像的剪切(Cut)、复制(Copy)、粘贴(Paste)等功能。剪贴板可以理解为一块存储数据的公共区域,用户可以用复制(Copy)或剪切(Cut)菜单项把数据放入到剪贴板中,当本任务或其它任务要用剪贴板中的数据时,可以用粘贴(Paste)菜单项从剪贴板中把数据取出。存入剪贴板中的数据,可以是字符,位图,或者其它格式数据。在图形模式下使用剪贴板包括如下菜单项:选定剪切区域、剪切(Cut)、复制(Copy)、粘贴(Paste)等。使过画图程序的读者都知道,在使用剪切和复制前,必须首先选定剪切或复制区域,首先按一个按钮,通知程序要设定剪切或复制区域,然后在要选定区域的左上角按下鼠标左键,拖动鼠标画出一个矩形,抬起鼠标后显示一个矩形既为设定的剪切或复制区域。剪切或复制图形或图像到剪贴板后,矩形自动消失。下面详细介绍实现以上功能的方法。
5.12.1 剪切复制区域选定
剪切复制区域选定的方法和前边章节中拖动鼠标绘制椭圆(圆)的方法基本一样,只是在这里绘制的是矩形,而且在鼠标抬起时,不把矩形存入PictureBox控件属性Image引用的位图对象中,仅仅记录矩形的位置。请读者自己实现此功能。
5.12.2 剪切板复制功能的实现
假定已选定复制区域,例如为区域Rectangle(10,10,50,50),把此区域的图形或图像放到剪贴板中。具体实现步骤如下:
(1) 新建项目。放PictureBox控件到窗体,修改属性Dock=Fill。属性Name=pictureBox1,修改属性Image为一图像文件全路径,使其显示一幅图。
(2) 把Mainmenu控件放到主窗体中。增加顶级菜单项:编辑,属性Name=menuItemEdit。为"编辑"顶级菜单项的弹出菜单增加菜单项:复制、剪切、粘贴。属性Name分别为menuItemCopy、menuItemCut、menuItemPaste。
(3) 为主窗体菜单"编辑"|"复制"菜单项增加单击事件处理函数如下:
private void menuItemCopy_Click(object sender, System.EventArgs e)
{ Bitmap myBitmap=new Bitmap(pictureBox1.Image);
Rectangle cloneRect=new Rectangle(10,10,50,50);
System.Drawing.Imaging.PixelFormat format=myBitmap.PixelFormat;
Bitmap cloneBitmap=myBitmap.Clone(cloneRect,format);
Clipboard.SetDataObject(cloneBitmap);
}
(4) 运行,单击菜单"编辑"|"复制"菜单项,复制图形到剪贴板。打开Windows画图程序,单击画图程序菜单"编辑"|"粘贴"菜单项,被复制的图形应能正确粘贴到画图程序中。
5.12.3 剪贴板剪切功能的实现
(5) 剪切是先复制,再把选中区域图形清除,菜单"编辑"|"剪切"菜单项事件处理函数如下:
private void menuItemCut_Click(object sender,System.EventArgs e)
{ menuItemCopy_Click(sender,e);//调用复制菜单项单击事件处理函数
Bitmap bits=new Bitmap(50,50);//建立位图对象,宽和高为选中区域大小
Graphics g=Graphics.FromImage(bits);//得到位图对象的Graphics类的对象
g.Clear(Color.White);//用白色清除位图对象中的图像
Bitmap myBitmap=new Bitmap(pictureBox1.Image);
g=Graphics.FromImage(myBitmap);
g.DrawImage(bits,10,10,50,50);
pictureBox1.Image=myBitmap;//位图对象显示在pictureBox1中,即清除剪切区域
}
(6) 运行,单击主窗体菜单"编辑"|"剪切"菜单项,设定区域的图形被复制到剪切板,原位置图形被清空为白色。
5.12.4 剪贴板粘贴功能的实现
(7) 为主窗体菜单"编辑"|"粘贴"菜单项增加单击事件处理函数如下:
private void menuItemPaste_Click(object sender, System.EventArgs e)
{ IDataObject iData=Clipboard.GetDataObject();//得到剪贴板对象
if(iData.GetDataPresent(DataFormats.Bitmap))//判断剪贴板有无位图对象
{ Bitmap bits=(Bitmap)iData.GetData(DataFormats.Bitmap);//得到剪贴板位图
Bitmap myBitmap=new Bitmap(pictureBox1.Image);
Graphics g=Graphics.FromImage(myBitmap);
g.DrawImage(bits,30,30);//粘贴图形左上角到坐标(30,30)位置
pictureBox1.Image=myBitmap;//位图对象在pictureBox1中显示
}
}
(8) 运行Windows画图程序,拷贝图形到剪贴板。运行自己编制的程序,单击菜单"编辑"|"粘贴"菜单项,可以看到画图程序中被拷贝的图形能正确粘贴到自己编制的程序中。
(9) Windows画图程序粘贴后,能用鼠标拖动粘贴的图形,现实现此功能。放PictureBox控件到窗体,属性Name=pictureBox2,属性Visable=false。将把粘贴后的图形放到PictureBox2中,使其移动。为Form1类增加变量:bool mark=false;int x=0,y=0;为pictureBox2控件事件OnMouseDown,OnMouseUp,OnMouseMove增加事件处理函数如下:
private void pictureBox2_MouseDown(object sender,//鼠标按下事件处理函数
System.Windows.Forms.MouseEventArgs e)
{ mark=true;
x=e.X;
y=e.Y;
}
private void pictureBox2_MouseMove(object sender,//鼠标移动事件处理函数
System.Windows.Forms.MouseEventArgs e)
{ if(mark)
{ int x1,y1;
x1=e.X-x;
y1=e.Y-y;
pictureBox1.Invalidate();//擦除上次鼠标移动时画的图形
pictureBox1.Update();//立即重画,即擦除
pictureBox2.Left+=x1;//效果是拖动pictureBox2随鼠标移动
pictureBox2.Top+=y1;
x=e.X;
y=e.Y;
}
}
private void pictureBox2_MouseUp(object sender,
System.Windows.Forms.MouseEventArgs e)
{ mark=false;}
(10) 修改主窗体菜单项粘贴单击事件处理函数如下:
private void menuItemPaste_Click(object sender,System.EventArgs e)
{ IDataObject iData=Clipboard.GetDataObject();
if(iData.GetDataPresent(DataFormats.Bitmap))
{ Bitmap bit=(Bitmap)iData.GetData(DataFormats.Bitmap);
pictureBox2.Width=bit.Width;//阴影为修改部分
pictureBox2.Height=bit.Height;
pictureBox2.Image=bit;
pictureBox2.Top=pictureBox1.Top;
pictureBox2.Left=pictureBox1.Left;
pictureBox2.Parent=pictureBox1;
pictureBox2.Visible=true;
}
}
(11) 在pictureBox1控件任意位置单击鼠标,表示已将粘贴图像拖到指定位置,需将粘贴图像粘贴到pictureBox1控件。为pictureBox1控件的事件OnMouseDown增加事件函数如下:
private void pictureBox1_MouseDown(object sender,
System.Windows.Forms.MouseEventArgs e)
{ if(pictureBox2.Image!=null&&pictureBox2.Visible)
{ Bitmap bits=new Bitmap(pictureBox2.Image);
Bitmap myBitmap = new Bitmap(pictureBox1.Image);
Graphics g=Graphics.FromImage(myBitmap);
g.DrawImage(bits,pictureBox2.Left,pictureBox2.Top);
pictureBox1.Image=myBitmap;//位图对象在pictureBox1中显示
pictureBox2.Visible=false;
}
}
(12) 运行Windows画图程序,拷贝图形到剪贴板。运行自己编制的程序,单击菜单"编辑"|"粘贴"菜单项,可以看到画图程序中被拷贝的图形能正确粘贴到自己编制的程序中。拖动被拷贝的图形,使其运动到指定位置,在pictureBox2外,单击鼠标右键,图形固定到指定位置。在拖动图像时,图像抖动。为避免抖动,不必每次都响应鼠标移动事件,可以多次事件响应一次,读者可以试一下。
5.13 图像的处理
本节介绍图像的处理的最基础知识,要想深入了解这方面的知识,还要读这方面的专著。
5.13.1 图像的分辨率
例子e5_13_1:将原图形的分辨率降低16倍,其方法是将原图形分成4*4的图形块,这16个点的颜色都置成这16个点中某点的颜色,例如4*4的图形块左上角的颜色。
(1) 新建项目。放两个PictureBox控件到窗体,属性Name分别为pictureBox1,pictureBox2,设置pictureBox1属性Image,使其显示一幅图。PictureBox2显示变换后的图形。
(2) 放Button控件到窗体,为其增加事件处理函数如下:
private void button1_Click(object sender,System.EventArgs e)
{ Color c;
int i,j,size,k1,k2,xres,yres;
xres=pictureBox1.Image.Width;//pictureBox1显示的图像的宽
yres=pictureBox1.Image.Height;//pictureBox1显示的图像的高
size=4;
pictureBox2.Width=xres;//令pictureBox2和pictureBox1同宽,同高。
pictureBox2.Height=yres;
Bitmap box1=new Bitmap(pictureBox1.Image);
Bitmap box2=new Bitmap(xres,yres);
for(i=0;i //将第i行,将第j列开始的4*4图形块中所有点的颜色置为左上角颜色 { for(j=0;j { c=box1.GetPixel(i,j);//得到4*4图形块左上角颜色值 for(k1=0;k1<=size-1;k1++)//4*4图形块中所有点的颜色置为左上角颜色 { for(k2=0;k2<=size-1;k2++) if((i+k1) box2.SetPixel(i+k1,j+k2,c); } } } pictureBox2.Image=box2; } (3) 运行,单击按钮,在PictureBox2中可以看到分辨率低的图形。 例子e5_13_2:本例把彩色图像变换为灰度图像。其方法是将原彩色图形每一个点的颜色取出,求出红色、绿色、蓝色三个分量的平均值,即(红色+绿色+蓝色)/3,作为这个点的红色、绿色、蓝色分量,这样就把彩色图像变成了灰度图像。具体步骤如下: (1) 新建项目。放两个PictureBox控件到窗体,属性Name分别为pictureBox1,pictureBox2,设置pictureBox1属性Image使其显示一幅彩色图。PictureBox2显示变换后的黑白图形。 (2) 放Button控件到窗体,为其增加事件函数如下: private void button1_Click(object sender, System.EventArgs e) { Color c; int i,j,xres,yres,r,g,b; xres=pictureBox1.Image.Width; yres=pictureBox1.Image.Height; pictureBox2.Width=xres; pictureBox2.Height=yres; Bitmap box1=new Bitmap(pictureBox1.Image); Bitmap box2=new Bitmap(xres,yres); for(i=0;i { for(j=0;j { c=box1.GetPixel(i,j);//得到此点彩色颜色值 r=c.R; g=c.G; b=c.B; r=(r+g+b)/3;//求红、绿、蓝三基色的平均值 c=Color.FromArgb(r,r,r);//彩色变为灰度 box2.SetPixel(i,j,c); } } pictureBox2.Image=box2; } (3) 运行,单击按钮,在PictureBox2中可以看到黑白图像。 例子e5_13_3:将一幅灰度图像变换为另一幅灰度图像,两幅灰度图形的灰度满足如下关系:设图1和图2的灰度分别为d1和d2,如d1<85,d2=0;如85<=d1<=170,d2=(d1-85)*3;如d1>170,d2=255。变换的效果是增强了对比度。具体步骤如下: (1) 新建项目。放两个PictureBox控件到窗体,属性Name分别为pictureBox1,pictureBox2,修改pictureBox1属性Image,使其显示一黑白幅图。PictureBox2显示变换后的图形。 (2) 放Button控件到窗体,为其增加事件函数如下: private void button1_Click(object sender, System.EventArgs e) { Color c; int i,j,xres,yres,m; xres=pictureBox1.Image.Width; yres=pictureBox1.Image.Height; pictureBox2.Width=xres; pictureBox2.Height=yres; Bitmap box1=new Bitmap(pictureBox1.Image); Bitmap box2=new Bitmap(xres,yres); int[] lut=new int[256];//索引是源图像灰度值,元素值是变换后的灰度值 for(i=0;i<85;i++)//d1<85 lut[i]=0;//d2=0 for(i=85;i<=170;i++)//85<=d1<=170 lut[i]=(i-85)*3;//d2=(d1-85)*3 for(i=171;i<256;i++)//d1>170 lut[i]=255;//d2=255 for(i=0;i { for(j=0;j { c=box1.GetPixel(i,j); m=lut[c.R]; c=Color.FromArgb(m,m,m); box2.SetPixel(i,j,c); } } pictureBox2.Image=box2; } (3) 运行,单击按钮,在PictureBox2中可以看到对比度增强的黑白图形。 学习了以上知识,制作一些简单动画是比较容易的。例如,如果一段动画,要求一个动画小人从主窗体左侧走到右侧,如何实现呢?首先,为了看到人在走动,应该有3个动作:右脚在前,左脚在后;两脚并排;左脚在前,右脚在后。因此应制作3幅图画,表示这三个动作。每当转换一幅图画,图画应在X轴方向右移一步的距离。将3幅图画放到3个PictureBox控件中,用定时器产生定时事件,定时事件处理函数负责使其中一幅图画显示,其余两幅不显示,同时,修改PictureBox控件属性Left,使其在正确的位置上。这样就可以看到人的走动了。请读者自己完成这段动画。5.13.2 彩色图像变换为灰度图像
5.13.3 灰度图像处理
5.13.4 动画