第五章 图形图像编程 本章的目的是学习使用GDI+的图形图像编程方法,希望在学习本章以后,能编制像Windows画图那样的程序。本章的重点是学习创建画笔、画刷等绘图工具,学习使用Graphics类的绘图函数绘制各种图形,学习利用Bitmap类处理图形和图像,最后简单介绍多媒体和3D图形程序设计的基础知识。
5.1 GDI+(图形设备环境接口) GDI+是2D(2维)图形库,它允许程序员用库中的函数编写与显视器、打印机和文件等图形设备进行交互的Windows和Web应用程序,可以在这些设备上输出字符和2D图形。Windows和Web应用程序根据是在CRT显示还是在打印机打印,首先生成显示设备环境或打印设备环境,然后调用不同设备环境中的同名函数实现在显视器显示或在打印机上打印。而设备环境根据选择的不同设备,调用各个设备制造厂商提供的设备驱动程序,在显视器上显示或在打印机上打印。这些工作都是由GDI+完成的。这样做的最大好处是应用程序和设备无关,应用程序不必为不同的设备编制不同的程序。无论是不同的显卡,还是不同的打印机,只要安装该设备的驱动程序,应用程序就可以使用该设备了,微软的Word程序可以使用不同的打印机就是使用了这个原理。设备环境所扮演的角色如图所示。
CRT 显示 打印机 打印 驱动程序 (GDI+) 设 备 环 境 应 用 程 序
GDI+是对Windows操作系统GDI函数库(GDI32 GDI+是对Windows操作系统GDI函数库(GDI32.DLL)的扩充,并用C++类进行了封装,GDI+提供了比GDI更强大的功能。WindowsXP及以后版本支持GDI+,以前版本Windows操作系统要使用GDI+,必须复制Gdiplus.dll到系统目录。GDI+支持受控代码和非受控代码。
5.2 Graphics类 Graphics类提供一些方法绘制各种图形。Graphics类是密封类,不能有派生类。
5.2.1 使用Graphics类绘图的基本步骤 GDI+类提供了一些绘图工具,例如提供笔用来定义图形外轮廓线的颜色及粗细,提供刷子定义添充封闭图形内部的颜色和格式,提供不同输出字体。Graphics类中提供绘图函数使用GDI+类中提供的绘图工具来输出字符或绘制图形。GDI+默认绘图坐标以窗体用户区左上角为原点,x轴向右为正方向,y轴向下为正方向,单位为像素。GDI+定义了三种坐标系统,并提供了三种坐标转换的方法Graphics.TransformPoints()。在不同的控件或设备上绘制图形方法有很大不同,为了在不同的控件或设备上用完全相同的代码完成相同的图形,绘图前应首先建立或得到相应控件或设备关联的Graphics类对象。下面的例子,在窗体中增加了一个按钮,单击按钮将在窗体中画一个边界为红色,内部填充为蓝色的圆。该程序段说明了使用Graphics类绘图的基本步骤。按钮的单击事件处理函数如下:
绘制矩形定义的园或椭圆,矩形左上角坐标为(10,10),宽和高各为100个像素单位。 private void button1_Click(object sender,EventArgs e) { //得到窗体使用的Graphics类对象方法1 Graphics g=this.CreateGraphics(); //创建红色的笔对象 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事件处理函数。修改上例,在Form1类中增加Paint事件处理函数如下:
private void Form1_Paint(object sender,PaintEventArgs e) { //得到窗体的使用的Graphics类对象方法2,方法3见5.9.4节 Graphics g=e.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+中常用的结构,包括:Point、PointF、Size、SizeF、Rectangle、RectangleF、Color等。它们是在命名空间System.Drawing中定义的。 点结构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为浮点数
结构Size和SizeF用来描述对象宽和高,有成员Width和Height。常用构造函数如下: //坐标系统可以转换,坐标单位可以不是点距 Size(int width,int height); //float类型是为了支持坐标不是点距的单位 //(例如单位为mm) SizeF(float width,float height);
矩形结构Rectangle和RectangleF用来描述一个矩形,其常用属性和方法如下: 属性X、Y和只读属性Left、Top: 只读属性Right和Bottom: 属性Width、Height和只读属性Size: 构造函数Rectangle(Point location,Size size): 构造函数Rectangle(int X,int Y,int width,int height):
Color结构用来描述颜色。任何一种颜色可以用透明度(alpha),蓝色(bb),绿色(gg),红色(rr)合成,16进制数格式为0xalrrbbgg,其中al,bb,gg,rr为2位16进制数(0-255),用这个无符号32位数代表颜色。Color结构常用方法如下: public static Color FromArgb (int alpha,int rr,int gg,int bb): (int rr,int gg,int bb): (int alpha,Color color): 【例5.1】本例创建3个半透明的红、绿、蓝刷子,填充3个矩形,请注意例子中透明度及颜色使用的方法。
private void Form1_Paint (object sender,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 效果如图,可以将透明度alpha值设为255,再运行一次,看看有何不同。C#中还预定义了一些颜色常数,例如黑色为Color.Black,红色为Color.Red等等,请用VS2005帮助系统查看。
5.4 画笔Pen类 Pen类对象指定绘制图形的外轮廓线宽度和颜色。Pen类有4个构造函数,分别是: public Pen(Color color); public Pen(Color color,float width); public Pen(Brush brush); public Pen(Brush,float width); 【例5.2】Pen类常用的属性:Color为笔的颜色,Width为笔的宽度,DashStyle为笔的样式,EndCap和StartCap为线段终点和起点的外观。下例显示各种笔的属性DashStyle、EndCap和StartCap不同选项的样式。主窗体Paint事件处理函数如下,运行效果如图。
private void Form1_Paint(object sender,PaintEventArgs e) { Graphics g=e.Graphics; Pen pen1=new Pen(Color.Red,6); //默认为实线笔 //从点(10,10)到点(100,100)的实线,图中左边第1条线 g.DrawLine(pen1,10,10,100,10); 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条线 //下条语句设置双点,短线风格的线风格 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= //前箭头 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条线 System.Drawing.Drawing2D.LineCap.DiamondAnchor; g.DrawLine(pen1,150,70,250,70); //图中右边第6条线 }
5.5 创建画刷 画刷类对象指定填充封闭图形内部的颜色和样式,封闭图形包括矩形、椭圆、扇形、多边形和任意封闭图形。GDI+系统提供了几个预定义画刷类,包括: SolidBrush:单色画刷,在命名空间System.Drawing中定义。 HatchBrush:阴影画刷,后4个画刷在命名空间System.Drawing.Drawing2D中定义。 TextureBrush:纹理(图像)画刷。 LinearGradientBrush:两个颜色或多个颜色线性渐变画刷。 PathGradientBrush:使用路径定义刷子形状的复杂渐变画刷。
5.5.1 单色画刷SolidBrush 前边已使用过单色画刷。其构造函数只有1个,定义如下: //建立指定颜色的画刷 SolidBrush brush1=new SolidBrush(Color color); 可以使用属性Color来修改画刷颜色,例如:brush1.Color=Color.Green;
5.5.2 阴影画刷HatchBrush 阴影画刷是指定样式(例如,多条横线、多条竖线、多条斜线等)、指定填充线条的颜色和指定背景颜色的画刷,阴影画刷常用属性和方法如下: 属性backgroundColor:画刷背景颜色。 属性foreColor:画刷填充线条的颜色。 属性HatchStyle:该属性是只读的,不能修改,表示画刷的不同样式。 构造函数HatchBrush(HatchStyle h,Color c):参数1指定样式,参数2指定填充线条的颜色,背景色被初始化为黑色。 构造函数HatchBrush(HatchStyle h,Color c1,Color c2):参数1指定样式,参数2指定填充线条的颜色,参数3指定背景色。
【例5. 3】例子显示了属性HatchStyle为不同值时阴影画刷的不同样式。在Form1. cs文件头部增加语句using System 【例5.3】例子显示了属性HatchStyle为不同值时阴影画刷的不同样式。在Form1.cs文件头部增加语句using System.Drawing.Drawing2D,窗体Paint事件处理函数如下: private void Form1_Paint(object sender,PaintEventArgs e) { Graphics g=e.Graphics; //得到窗体使用的Graphics类对象 HatchBrush b1=new HatchBrush(HatchStyle.BackwardDiagonal, Color.Blue,Color.LightGray); //矩形被填充左斜线,下图窗体中第1图 g.FillRectangle(b1,10,10,50,50); HatchBrush b2=new HatchBrush (HatchStyle.Cross,Color.Blue,Color.LightGray); g.FillRectangle(b2,70,10,50,50);//矩形被填充方格,第2图 HatchBrush b2=new HatchBrush(HatchStyle.Cross,
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, g.FillRectangle(b4,190,10,50,50);//矩形被填充菱形,第4图 HatchBrush b5=new HatchBrush(HatchStyle.Vertical, g.FillRectangle(b5,250,10,50,50);//矩形被填充竖线,第5图 HatchBrush b6=new HatchBrush(HatchStyle.Horizontal, g.FillRectangle(b6,310,10,50,50);//矩形被填充横线,第6图 } //运行效果如图
5.5.3 纹理(图像)画刷TextureBrush 纹理(图像)画刷使用图像填充封闭曲线的内部,有8个构造函数,最简单的构造函数如下,参数为画刷使用的位图类对象,其余请读者使用MSDN或VS2005帮助系统查看。TextureBrush(Image bitmap); 【例5.4】例子使用位图文件n2k.bmp建立位图类对象作为画刷的图案,在Form1.cs文件的头部增加语句using System.Drawing.Drawing2D,窗体Paint事件处理函数如下: n2k.bmp是Windows2000操作系统中的一个位图文件,应用程序运行效果如图。
private void Form1_Paint(object sender,PaintEventArgs e) { Graphics g=e.Graphics;//得到窗体使用的Graphics类对象 Pen pen1=new Pen(Color.Red); //建立位图类对象见5.10节 Bitmap b1=new Bitmap("C:\\WINNT\\system32\\n2k.bmp"); TextureBrush b1=new TextureBrush(b1); g.FillRectangle(b1,10,10,200,100); g.DrawRectangle(pen1,10,10,200,100); }
5.5.4颜色渐变画刷LinearGradientBrush 5.5.5画刷PathGradientBrush和GraphicsPath类 自学
5.6 基本图形的绘制和填充 Graphics类提供了一些方法,用来绘制或填充各种图形。本节介绍这些方法。
5.6.1 用DrawLine方法绘制线段 绘制线段方法定义如下: void DrawLine(Pen pen,int x1,int y1,int x2,int y2): DrawLine(Pen pen,Point p1,Point p2): public void DrawLines(Pen pen,Point[] points): 【例5.7】使用DrawLine()的例子,为主窗体Paint事件增加事件处理函数如下:
private void Form1_Paint (object sender,PaintEventArgs e) { Graphics g=e.Graphics; Pen pen1=new Pen(Color.Red); //用笔pen1从点(30,30)到(100,100)画直线 g.DrawLine(pen1,30,30,100,100); Point p1=new Point(30,40); Point p2=new Point(100,110); //用笔pen1从点(30,40)到(100,110)画直线 g.DrawLine(pen1,p1,p2); }
【例5.8】使用绘制线段函数画任意曲线(画正弦曲线,注意如何使用数学函数)。 private void Form1_Paint(object sender,PaintEventArgs e) { //得到窗体的使用的Graphics类对象 Graphics g=this.CreateGraphics(); 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; } } //运行,在窗体中可以看到一条红色正弦曲线如图
【例5.9】在Windows画图程序中,可以拖动鼠标画任意曲线。本例实现用拖动鼠标左键在主窗体中画曲线。每条曲线都是由若干很短的线段组成。鼠标左键在按下状态,移动鼠标,每次移动很短距离,画出这段线段,所有这些线段组合起来,形成一条曲线。 新建项目。在Form1类中增加两个私有变量: //表示鼠标左键是否按下,如按下鼠标再移动将画曲线 private bool mark=false; //记录画下一条很短线段的起始点 private Point point; 为主窗体的事件OnMouseDown,OnMouseUp,OnMouseMove增加事件处理函数如下:
private void Form1_MouseDown (object sender,MouseEventArgs e) { if(e.Button==MouseButtons.Left) //如果鼠标左键按下 { //记录曲线的第一个点的坐标,为画线段的起始点 point.X=e.X; point.Y=e.Y; mark=true;//表示鼠标左键已按下,鼠标如果再移动,将画曲线 } private void Form1_MouseMove { if(mark) //如果鼠标左键已按下 { //得到窗体的使用的Graphics类对象 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; } private void Form1_MouseUp (object sender,MouseEventArgs e) { mark=false; } //表示鼠标移动不再画线 运行,在主窗体中拖动鼠标左键可以画线。但最小化后再最大化后,图形消失。
现修改上例,使其克服这个缺点。实现的思路是记录每一条曲线的每一条很短线段的坐标。使用List类对象记录曲线以及曲线中的点,请注意List类使用方法。 在定义主窗体的Form1类中增加私有变量: //用来记录1条曲线的所有点。 private List<Point> Point_List; //用来记录每条曲线,既Point_List Private List<List<Point>> Line_List; 在Form1类构造函数中增加语句:Line_List = new List<List<Point>>(); 修改主窗体事件OnMouseDown,OnMouseUp,OnMouseMove事件处理函数如下:
private void Form1_MouseDown (object sender,MouseEventArgs e) { if(e.Button==MouseButtons.Left) { //建立数组,记录1条曲线的所有点 Point_List=new List<Point>(); point.X=e.X; point.Y=e.Y; mark=true; Point_List.Add(point);//记录曲线起点的坐标 }
private void Form1_MouseMove (object sender,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 { mark=false; //记录此条线,注意参数是Point_List Line_List.Add(Point_List);
增加主窗体的Paint事件处理函数如下,该函数重画记录的所有曲线。 private void Form1_Paint (object sender,PaintEventArgs e) { Graphics g=e.Graphics; Pen pen1=new Pen(Color.Black); Point p1,p2; foreach (List<Point> l in Line_List)//取出每条线 { for(int k=0;k<(l.Count-1);k++)//重画每条线的点 { p1= l[k]; p2= l[k+1]; g.DrawLine(pen1,p1,p2); } } //运行,在Form窗体拖动鼠标可以画线。 } //最小化后再最大化后,图形不消失。
5.6.2 泛型List类 在C#2.0中程序中可用泛型List 类替换 C#1.x中的ArrayList 类,List 类在大多数情况下比ArrayList 类性能更好并且类型安全。List类是容量可以动态增加的数组,其元素可以是任意类型,但在使用前必须指定具体的数据类型。List类可以使用对象名[索引号]引用其元素,索引号从零开始。常用属性及方法如下: 属性Count:List中实际包含的元素数。 方法Add:将参数指定的对象添加到List对象的结尾处。 方法Clear:从List中移除所有元素。 方法Contains:bool类型,确定参数指定的元素是否在List中。
方法IndexOf:int类型,顺序查找和参数指定对象相同的第一个元素的索引。 方法Insert:插入数据,第1个参数为插入的位置(索引号),第2个参数为插入的对象。 方法LastIndexOf:顺序查找和参数指定对象相同的最后一个元素的索引。 方法RemoveAt:移除参数指定索引处的元素。 方法Sort:对整个List中的元素进行排序。
5.6.3 DrawEllipse方法画椭圆(圆)及键盘事件 画椭圆的两个方法如下,功能是画指定矩形的内切椭圆,如指定正方形则画圆。 void DrawEllipse(Pen pen,int x,int y,int width,int height):pen为画笔,画椭圆的边,(x,y)为矩形的左上角坐标,width为矩形的宽,height为矩形的高。 void DrawEllipse(Pen pen,Rectangle r):pen为画笔,画椭圆的边,r为矩形结构。 【例5.10】画椭圆。为主窗体Paint事件增加事件处理函数如下:
private void Form1_Paint (object sender,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); }
【例5.11】用键盘的四个箭头键移动窗体中的圆。移动圆,实际是先把前边画的圆擦掉,在新的位置重新画圆。如要擦除圆,可以用窗体背景色作为笔和刷子的颜色,在要擦除的圆的位置重画和填充圆。注意键盘事件处理函数的使用。具体实现步骤如下: 新建项目。在Form1类中增加变量:int x,y,记录定义圆位置的矩形左上角的坐标。 在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); }
为主窗体Paint事件增加事件处理函数如下: private void Form1_Paint (object sender,PaintEventArgs e) { DrawCir(Color.Red); } 为主窗体KeyDown事件增加事件函数如下:(注意不要使用KeyPress事件,其事件处理函数的第2个参数e的e.KeyChar是按下键的ASCII值,但很多键无ASCII值。) private void Form1_KeyDown(object sender,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; case Keys.Up: //圆上移 y=y-10; }
运行,可以用4个箭头键移动红色圆。键盘KeyDown事件处理函数的第2个参数e的e 运行,可以用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.6.4节到5.6.14请自学 5.7节请自学
5.8 图形框PictureBox控件 PictureBox控件常用于图形设计和图像处理程序,又称为图形框,该控件可显示和处理的图像文件格式有:位图文件(.bmp)、图标文件(.ico)、GIF文件(.gif)和JPG文件(.jpg)。该控件可以自己响应Paint事件,Paint事件处理函数用属性Image引用的位图对象重画图像。其常用的属性、事件和方法如下: 属性Image:指定要显示的图像,一般为Bitmap类对象。 属性SizeMode:指定如何显示图像,默认为Normal,图形框和要显示的图像左上角重合,只显示图形框相同大小部分,其余不显示;为CentreImage,图像和图形框中心点重合,图像四周超出图形框部分不显示;为StretchImage,缩放图像使之适合图片框。
方法CreateGraphics():得到图形框所使用的Graphics类对象。 方法Invalidate():要求控件对参数指定区域重画,如无参数,为整个区域。 方法Update():方法Invalidate()并不能使控件立即重画指定区域,只有使用Update()方法才能立即重画指定区域。使用见5.9.6节中的鼠标移动事件处理函数。 【例5.27】使用PictureBox控件显示图像 新建项目。放PictureBox控件到窗体。属性Name=pictureBox1。可以在设计阶段修改属性Image为指定图形文件,设定初始显示的图像。
放OpenFileDialog控件到窗体。属性Name=openFileDialog1。 放Button控件到窗体。属性Name=button1。button1控件单击事件处理函数如下: private void button1_Click(object sender,EventArgs e) { if(openFileDialog1.ShowDialog()==DialogResult.OK) { //Bitmap类见下节 Bitmap p1=new Bitmap(openFileDialog1.FileName); pictureBox1.Image=p1; }
5.9 Bitmap类 System.Drawing命名空间有一个类Image,用来处理图像。Image类的派生类Bitmap类封装了GDI+中的位图,可以处理由像素数据定义的图像。Image类的派生类metafile类处理图元图形,metafile类用记录绘图命令的方法来存储图像。
5.9.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.9.2 Bitmap类的方法 方法SetPixel():画点方法,前2个参数是指定点的位置,第3个参数是颜色值。 方法GetPixle():得到指定点的颜色,2个参数是指定点的位置,返回颜色值。 有多个构造函数例如:new Bitmap(“图像文件名”),new Bitmap(宽,高)等。 方法Save():第1个参数是文件名,第2个参数是指定文件存为那种类型,可以是如下类型:System.Drawing.Imaging.ImageFormat.bmp(或.ico、.gif、.jpg)。 方法Dispose():释放位图对象 方法Clone():参数1是矩形结构,表示克隆区域。参数2表示单位。见5.10.2节。
5.9.3 SetPixel方法画点 【例5.28】用SetPixel方法画点,GetPixle方法得到指定点的颜色。放Button和PictureBox控件到主窗体。增加Button控件单击事件处理函数如下: private void button1_Click (object sender,EventArgs e) { pictureBox1.Width=720;//设定pictureBox1的宽和高 pictureBox1.Height=110; //建立位图对象,宽=720,高=110 Bitmap bits=new Bitmap(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中显示 pictureBox1.Image=bits; Color c1=bits.GetPixel(20,20); string s="R="+c1.R+",G="+c1.B+",G+"+c1.G; MessageBox.Show(s); }
5.9.4 在PictureBox中拖动鼠标画曲线 【例5.29】例5.9实现了画任意曲线,用List类记录绘制的曲线,在该例中增加橡皮、图像的拷贝、剪切和粘贴功能比较困难,也不能和Windows画图程序交换文件。可用图形框(PictureBox控件)实现以上功能。图形框显示图像被破坏需恢复时,图形框自动响应Paint事件,用属性Image引用的位图对象恢复所绘制的图形。因此绘制图形必须记录到图形框属性Image引用的位图对象中,才能被保存,仅将图形绘制在图形框表面,图形框响应Paint事件,绘制的图形将不能被恢复,既绘制在图形框表面的图形丢失了。实现步骤如下:
新建项目。为Form1类增加4个私有变量:private bool mark=false; private Point point; private Bitmap bits; private Graphics bitG; 放PictureBox控件到窗体,修改属性Dock=Fill。 在构造函数中增加语句: bits=new Bitmap(pictureBox1.Width, pictureBox1.Height); //建立位图类对象,宽和高为指定值 //得到位图对象的Graphics类的对象方法3 bitG=Graphics.FromImage(bits); bitG.Clear(Color.White);//用白色清除位图对象中的图像 pictureBox1.Image=bits;//bits记录了pictureBox1显示的图像 为控件PictureBox事件OnMouseDown,OnMouseUp,OnMouseMove增加事件处理函数:
private void pictureBox1_MouseDown (object sender,MouseEventArgs e) { if(e.Button==MouseButtons.Left)//是否是鼠标左键按下 { point.X=e.X; point.Y=e.Y; //画线段开始点 mark=true; } //鼠标左键按下标识 } private void pictureBox1_MouseUp { mark=false; pictureBox1.Image=bits; } //保存了所画的图形
private void pictureBox1_MouseMove (object sender,MouseEventArgs e) { if(mark) //如果鼠标左键按下 { Graphics g=pictureBox1.CreateGraphics(); Pen pen1=new Pen(Color.Black); //图形画在PictureBox表面 g.DrawLine(pen1,point.X,point.Y,e.X,e.Y); //图形画在位图对象bits中 bitG.DrawLine(pen1,point.X,point.Y,e.X,e.Y); point.X=e.X; point.Y=e.Y; }//下次绘制画线段开始点 } 运行,在PictureBox控件拖动鼠标可以画线。最小化后再最大化后,图形不消失。
5.9.5 存取位图文件 【例5.30】为上例增加存取位图文件功能。 把MenuStrip控件放到主窗体中。增加顶级菜单项:文件。为“文件”顶级菜单项的弹出菜单增加菜单项:新建、打开、另存为、退出。 为主窗体菜单“文件|新建”菜单项增加单击事件处理函数如下: private void 新建ToolStripMenuItem_Click (object sender, EventArgs e) { bitG.Clear(Color.White);//用白色清空位图对象bitG pictureBox1.Image=bits; } //pictureBox1显示用白色清空位图对象bitG
放OpenFileDialog控件到窗体。菜单“文件|打开”菜单项单击事件处理函数如下: private void 打开ToolStripMenuItem_Click (object sender, EventArgs e) { if(openFileDialog1.ShowDialog(this)== DialogResult.OK) { bits.Dispose(); //撤销bitG所引用的对象 //建立指定文件的新位图对象 bits=new Bitmap(openFileDialog1.FileName); //得到位图对象使用的Graphics类对象 bitG=Graphics.FromImage(bits); pictureBox1.Image=bits; }
放SaveFileDialog控件到窗体。菜单“文件|另存为”菜单项单击事件处理函数如下: private void 另存为ToolStripMenuItem_Click (object sender, EventArgs e) { if(saveFileDialog1.ShowDialog(this)== DialogResult.OK) { string s=saveFileDialog1.FileName+".bmp"; bits.Save(s, System.Drawing.Imaging.ImageFormat.Bmp); } //也可以存为其他格式,例如:Jpg,Gif等。请读者试一下。
为主窗体菜单“文件|退出”菜单项增加单击事件处理函数如下: private void 退出ToolStripMenuItem_Click (object sender, EventArgs e) { Close(); } 运行,在PictureBox控件拖动鼠标可以画线。存所画的图形到文件,再重新读出该文件,看是否正常运行。检查Windows画图程序能否打开本程序所存的图形文件。
5.9.6 用拖动鼠标方法画椭圆或圆 Windows画图程序用拖动鼠标方法画椭圆或圆,实现的方法是:以鼠标左键被按下处作为矩形的一个顶点,记为顶点1,该点坐标不改变。拖动鼠标移动到另一位置,以此位置作为矩形另一顶点,记为顶点2,顶点1和顶点2在矩形对角线的两端。绘制由顶点1和顶点2定义的矩形的内切椭圆,以显示要绘制椭圆的位置,这个椭圆的位置随着鼠标的移动而改变。鼠标抬起,以鼠标抬起位置为顶点2,用指定的画笔和画刷绘制由顶点1和顶点2定义的矩形的内切椭圆,作为最终图形。本节程序实现此功能。如果图形仅绘制在图形框(PictureBox控件)上,而不保存到其属性Image引用的位图对象中,当调用图形框的Invalidate()方法,发出Paint事件,Paint事件处理函数用图形框属性Image引用的位图对象恢复图像,将擦除仅绘制在图形框上的图形。拖动鼠标方法画椭圆或圆显示位置时,仅将椭圆或圆画在PictureBox上,在鼠标拖动显示下一个位置前,用图形框的Invalidate()方法擦除前一位置所画的图形。
新建项目。为Form1类增加5个变量:Point EndPoint; Point StartPoint; Bitmap bits; Graphics bitG; bool mark=false; 放PictureBox控件到子窗体。修改属性Dock=Fill。 在构造函数中增加语句:bits用来保存pictureBox1中位图图像,是pictureBox1属性Image引用的对象 bits=new Bitmap(pictureBox1.Width, pictureBox1.Height); //得到位图对象使用的Graphics类对象 bitG=Graphics.FromImage(bits); bitG.Clear(Color.White); pictureBox1.Image=bits;
在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)); } //返回矩形 为PictureBox事件OnMouseDown、OnMouseUp、OnMouseMove增加事件处理函数如下:
private void pictureBox1_MouseDown (object sender,MouseEventArgs e) { if(e.Button==MouseButtons.Left) { StartPoint.X=e.X;//以鼠标左键被按下处作为矩形的一个顶点 StartPoint.Y=e.Y;//StartPoint记录矩形的这个顶点 EndPoint.X=e.X;//拖动鼠标移动的位置作为矩形另一顶点 //EndPoint记录矩形的这个顶点,两个顶点定义一个矩形 EndPoint.Y=e.Y; mark=true; } //开始拖动鼠标画图标记 }
private void pictureBox1_MouseMove (object sender,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,MouseEventArgs e) { Pen pen1=new Pen(Color.Black); EndPoint.X=e.X; EndPoint.Y=e.Y; Rectangle r1=MakeRectangle(StartPoint,EndPoint); //最终椭圆画在pictureBox1属性Image引用的对象中 bitG.DrawEllipse(pen1,r1); mark=false; pictureBox1.Image=bits; } //显示画椭圆的最终结果 运行,在PictureBox控件中拖动鼠标可以画圆或椭圆。
本章以后的内容请自学