镜像图元

对图元进行镜像变换是要把图元相对于一个点或一条直线对称显示到另一面。比如在直角坐标系中,第1象限中的一个三角形相对于Y轴做镜像变换,则将三角形对称显示在第2象限中:若相对于原点做镜像变换,则对称显示在第3象限中。

本章讨论最通用的情况,即图元相对于任意直线进行镜像。相对于任意直线进行镜像变换时,需要进行5次变换。第1次进行平移变换,第2次进行旋转变换,第3次进行镜像变换,然后,先后做第2次和第1次变换的逆变换。所以,相对于任意直线的镜像变换的变换矩阵如下所示:

Document Image
\[\]

故,变换后点的坐标为:

Document Image
\[\]

对于两个点(x,.y)和(x2,yz)确定的一条直线段,其截距方程中的常数项a可用下式求得:

Document Image
\[\]

参数0是点(xi,yi)和(x2,y)所确定的直线段的方位角。

这样,图元控制点旋转以后的坐标值可以求得。实际编程时,需要首先在CGElement类中添加一个需要重写的Mirror方法,然后在各派生类中重写它。图元类更新以后,创建一个旋转交互类CMirror,它定义触发鼠标事件时的绘图行为。

更新图元类

1. 在CGElement类中添加Mirror方法

需要首先在CGElement 类中添加1个需要重写的Mirror方法。利用该方法求取镜像以后图元控制点的位置。Mirror方法有3个参数,其中g为Graphics对象,指定绘图表面,pPos和pPos2分别定义代表镜像对称轴的直线段的起点和终点。

code.vc#.net
		//镜像图元
		abstract public void Mirror(Graphics g,PointF pPos1,
			PointF pPos2);

2. 在CLine类中重写基类的Mirror方法

在CLine类中重写基类的Mirror方法,计算镜像以后直线段起点和终点的坐标值。计算过程是通过pMirror函数实现的。pMirror函数有3个参数,Pos1和Pos2表示对称轴的起点和终点,Pos表不要镜像的点。函数返回一个PointF对象,为镜像后的点。

code.vc#.net
		//镜像变换
		override public void Mirror(Graphics g,PointF pPos1,PointF pPos2)
		{
			m_Begin = m.pMirror(pPos1, pPos2, m_Begin);
			m_End = m.pMirror(pPos1, pPos2, m_End);
		}
		//计算镜像以后点的坐标
		public PointF pMirror(PointF Pos1,PointF Pos2, PointF Pos){
			PointF pm=new PointF();
			float Angle;
			float cos2v,sin2v;
			float x1,y1;
			float x2,y2;
			float aa;
			float desX,desY;
			x1 =Pos1 .X;
			y1 =Pos1.Y;
			x2 =Pos2 .X;
			y2 = Pos2.Y;
			if(x2 == x1) {
				aa = 10000000;
			}
			else
			{
				aa = (x2 * y1 - x1 * y2) / (x2 - x1);
			}
			Angle = GetAngle(Pos1, Pos2);
			cos2v = (float)((Math.Cos(Angle * 2)));
			sin2v = (float)((Math.Sin(Angle * 2)));
			desX = Pos.X * cos2v + Pos.Y * sin2v - aa * sin2v;
			desY = Pos.X * sin2v - Pos.Y * cos2v + aa * cos2v + aa;
			pm.X = desX;
			pm.Y = desY;
			return pm;
		}

3. 在CRectangle类中重写基类的Mirror方法

在CRectangle类中重写基类的Miror方法时,用pMiror函数计算镜像以后矩形起点和终点的坐标值。

code.vc#.net
		//镜像变换
		override public void Mirror(Graphics g,PointF pPos1,PointF pPos2)
		{
			m_basePos = m.pMirror(pPos1, pPos2, m_basePos);
			m_desPos = m.pMirror(pPos1, pPos2, m_desPos);
		}

4. 在CCircle类中重写基类的Mirror方法

在CCircle类中重写基类的Mirror方法,获得圆相对于任意直线段镜像以后控制点的坐标值。

code.vc#.net
		//镜像变换
		override public void Mirror(Graphics g,PointF pPos1,PointF pPos2)
		{
			m_Center =m.pMirror(pPos1, pPos2, m_Center);
			m_pCircle = m.pMirror(pPos1, pPos2, m_pCircle);
		}

5. 在CArc类中重写基类的Mirror方法

在CArc类中重写基类的Mirror方法,获得圆弧相对于任意直线段镜像以后圆心、起点和终点的坐标值。需要注意的是,由于缺省时圆弧总是以逆时针方向画圆,所以镜像以后需要交换圆弧起点和终点的位置。

code.vc#.net
	   //镜像变换
	    override public void Mirror(Graphics g,PointF pPos1,PointF pPos2)
		{
	       //注意,圆弧的镜像变换要交换起点和终点的坐标
	       m_Center =m.pMirror(pPos1, pPos2, m_Center);
	       PointF pt;
	       pt = m.pMirror(pPos1, pPos2, m_Begin);
	       m_Begin = m.pMirror(pPos1, pPos2, m_End);
	       m_End = pt;
	   }

创建CMirror类

为了实现交互镜像,还需要创建一个CMirror类,专门定义触发鼠标事件时的响应行为。该类实现了ICommand接口。第1次单击鼠标左键时,确定镜像轴的起点,第2次单击鼠标左键时,确定镜像轴的终点。对于选择集中的图元,首先在屏幕上用删除模式重画,然后进行变换,并在变换后的位置上用选择模式重画。

code.vc#.net
	public class CMirror:ICommand
	{
		private int m_Step;
		private PointF m_Pos1;
		private PointF m_Pos2;
		public CMirror()
		{
			//
			// TODO: 在此处添加构造函数逻辑
			//
		}
		public void LButtonDown(Graphics g,PointF aPos,ArrayList ges,ArrayList geSels)
		{
			m_Step+=1;
			switch(m_Step)
			{
				case 1:
					m_Pos1=aPos;
					m_Pos2=aPos;
					break;
				case 2:
					//清除最后一条橡皮线
					CLine tempLine=new CLine(m_Pos1,m_Pos2);
					tempLine.Draw(g,DrawMode.Drag);
					tempLine=null;
					//将所有被选中的图元移动到目标位置并进行绘制
					if(geSels.Count>0)
					{
						for(int i=0;i< geSels.Count;i++)
						{
							//清除图元在当前位置上的图形
							((CGElement)(geSels[i])).Draw(g,DrawMode.Delete);
							((CGElement)(geSels[i])).Mirror(g,m_Pos1,m_Pos2);
							((CGElement)(geSels[i])).Draw(g,DrawMode.Selec);
						}
					}
					m_Step=0;
					break;
			}
		}
		public void MouseMove(Graphics g,PointF aPos)
		{
			switch(m_Step)
			{
			  case 1:
				  PointF prePos=new PointF();
				  PointF  curPos=new PointF();
				  prePos =m_Pos2;
				  curPos =aPos;
				  //清除上一条橡皮线
				  CLine tempLine1=new CLine(m_Pos1,prePos);
				  tempLine1.Draw(g,DrawMode.Drag);
				  tempLine1=null;
				  //绘当前位置的橡皮线
				  CLine tempLine2=new CLine(m_Pos1,curPos);
				  tempLine2.Draw(g,DrawMode.Drag);
				  tempLine2=null;
				  m_Pos2=aPos;
				  break;
			}
		}
		//单击鼠标右键时的绘图行为
		public void  RButtonDown(Graphics g,PointF aPos)
		{
			if(m_Step==1)
			{
				//清除上一条橡皮线
				CLine tempLine=new CLine(m_Pos1,m_Pos2);
				tempLine.Draw(g,DrawMode.Drag);
				tempLine=null;
			}
		}
	}

实现镜像图元

6.1.3节创建了用于镜像变换的菜单项mnuTMirror,下面创建一个CMirror类实例transMirror,然后用ICommand对象aCommand引用它。

code.vc#.net
		private CMirror trMirror=new Cmirror();
		private void mnuMirror_Click(object sender, System.EventArgs e)
		{
			aCommand=trMirror;
		}

使用"几何变换"菜单中的\"镜像变换\"选项,对图6-2中的图形进行镜像变换,得到图6-5所示的图形。

Document Image
\[\]

图6-5 镜像变换以后的图形