镜像图元

对图元进行镜像变换是要把图元相对于一个点或一条直线对称显示到另一面。比如在直角坐标系中,第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.vb.net
'镜像图元
Public MustOverride Sub Mirror(By Val g As Graphics,
            ByVal pPosI As PointF, By Val pPos2 As PointF)

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

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

code.vb.net
    '镜像变换
    Public Overrides Sub Mirror(ByVal g As Graphics, ByVal pPos1 As PointF, ByVal pPos2 As PointF)
        m_Begin = pMirror(pPos1, pPos2, m_Begin)
        m_End = pMirror(pPos1, pPos2, m_End)
    End Sub
    '计算镜像以后点的坐标
    Public Function pMirror(ByVal Pos1 As PointF, ByVal Pos2 As PointF, ByVal Pos As PointF) As PointF
        Dim pm As New PointF()
        Dim Angle, cos2v, sin2v As Single
        Dim x1, y1, x2, y2 As Single
        Dim aa, desX, desY As Single
        With Pos1
            x1 = .X
            y1 = .Y
        End With
        With Pos2
            x2 = .X
            y2 = .Y
        End With
        If x2 = x1 Then
            aa = 10000000
        Else
            aa = (x2 * y1 - x1 * y2) / (x2 - x1)
        End If
        Angle = GetAngle(Pos1, Pos2)
        cos2v = Cos(Angle * 2)
        sin2v = Sin(Angle * 2)
        desX = Pos.X * cos2v + Pos.Y * sin2v - aa * sin2v
        desY = Pos.X * sin2v - Pos.Y * cos2v + aa * cos2v + aa
        With pm
            .X = desX
            .Y = desY
        End With
        Return pm
    End Function

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

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

code.vb.net
    '镜像变换
    Public Overrides Sub Mirror(ByVal g As Graphics, ByVal pPos1 As PointF, ByVal pPos2 As PointF)
        m_basePos = pMirror(pPos1, pPos2, m_basePos)
        m_desPos = pMirror(pPos1, pPos2, m_desPos)
    End Sub

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

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

code.vb.net
    '镜像变换
    Public Overrides Sub Mirror(ByVal g As Graphics, ByVal pPos1 As PointF, ByVal pPos2 As PointF)
        m_Center = pMirror(pPos1, pPos2, m_Center)
        m_PCircle = pMirror(pPos1, pPos2, m_PCircle)
    End Sub

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

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

code.vb.net
    '镜像变换
    Public Overrides Sub Mirror(ByVal g As Graphics, ByVal pPos1 As PointF, ByVal pPos2 As PointF)
        '注意,圆弧的镜像变换要交换起点和终点的坐标
        m_Center = pMirror(pPos1, pPos2, m_Center)
        Dim pt As PointF
        pt = pMirror(pPos1, pPos2, m_Begin)
        m_Begin = pMirror(pPos1, pPos2, m_End)
        m_End = pt
    End Sub

创建CMirror类

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

code.vb.net
Public Class CMirror
    Implements ICommand
    Dim m_Step As Integer
    Dim m_Pos1, m_Pos2 As PointF
    '单击鼠标左键时的绘图行为
    Public Sub LButtonDown(ByVal g As Graphics, ByVal aPos As PointF) Implements ICommand.LButtonDown
        Dim i As Integer
        '鼠标单击次数加1
        m_Step += 1
        Select Case m_Step
            Case 1
                m_Pos1 = aPos
                m_Pos2 = aPos
            Case 2
                '清除最后一条橡皮线
                Dim tempLine As New CLine(m_Pos1, m_Pos2)
                tempLine.Draw(g, CGElement.geDrawMode.Drag)
                tempLine = Nothing
                '如果选择集中的图元个数大于0
                If geSels.Count > 0 Then
                    For i = 0 To geSels.Count - 1
                        '清除图元在当前位置上的图形
                        geSels(i).draw(g, CGElement.geDrawMode.Delete)
                        '将所有被选中的图元镜像到目标位置并进行绘制
                        geSels(i).Mirror(g, m_Pos1, m_Pos2)
                        geSels(i).Draw(g, CGElement.geDrawMode.Selec)
                    Next i
                End If
                m_Step = 0
        End Select
    End Sub
    '移动鼠标时的绘图行为
    Public Sub MouseMove(ByVal g As Graphics, ByVal aPos As PointF) Implements ICommand.MouseMove
        Select Case m_Step
            Case 1
                Dim prePos As New PointF()
                Dim curPos As New PointF()
                prePos = m_Pos2
                curPos = aPos
                '清除上一条橡皮线
                Dim tempLine1 As New CLine(m_Pos1, prePos)
                tempLine1.Draw(g, CGElement.geDrawMode.Drag)
                tempLine1 = Nothing
                '绘当前位置的橡皮线
                Dim tempLine2 As New CLine(m_Pos1, curPos)
                tempLine2.Draw(g, CGElement.geDrawMode.Drag)
                tempLine2 = Nothing
                m_Pos2 = aPos
        End Select
    End Sub
    '单击鼠标右键时的绘图行为
    Public Sub RButtonDown(ByVal g As Graphics, ByVal aPos As PointF) Implements ICommand.RButtonDown
        If m_Step = 1 Then
            '清除上一条橡皮线
            Dim tempLine As New CLine(m_Pos1, m_Pos2)
            tempLine.Draw(g, CGElement.geDrawMode.Drag)
            tempLine = Nothing
        End If
    End Sub
End Class

实现镜像图元

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

code.vb.net
Public transMirror As New Cmirror()
Private Sub mnuTMirror_Click(ByVal sender As System.Object,_
        ByVal e As System.EventArgs) Handles mnu_TMirror.Click
    aCommand = transMirror
End Sub

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

Document Image
\[\]

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