Gráficos vectoriales con Visual Basic .NET

Marzo 2003

Cipriano Valdezate Sayalero y Manuel Valdezate Sayalero

 


 

Matrices

 

Introducción

 

Transformar figuras consiste, como hemos visto, en rotar, trasladar y/o escalar su eje de coordenadas. Intuitivamente, sin embargo, no operamos con ejes de coordenadas, sino con los dibujos mismos. Podríamos, por tanto, definir transformación como un mapeado de puntos, tal que a cada punto de la figura original le correspondiera un punto en la figura transformada o final. El punto de la figura final se obtendría aplicando una operación al de la figura inicial, de tal modo que, si realizáramos una transformación determinada a una figura, todos los puntos de la figura final se obtendrían aplicando la misma operación a sus correspondientes puntos de la figura inicial. Esta operación es una multiplicación y/o suma de matrices, lo cual es lógico, pues podemos considerar un punto como una matriz [x,y] compuesta de una fila y dos columnas.

 

No nos vamos a adentrar en teoría de matrices ni pretendemos ser rigurosos en las definiciones, ni mucho menos nos preocuparemos aquí de demostrar nada algebráicamente, sino que nos limitaremos a exponer la mínima teoría imprescindible para entender el funcionamiento del objeto Matrix, que nos será de gran utilizad para personalizar nuestras transformaciones.

 

Una matriz es un conjunto de valores dispuestos en filas y columnas. En esto, una matriz es lo mismo que un array. Igual que los arrays, las matrices tienen dimensones. Las transformaciones de figuras bidimensionales utilizan matrices de dos dimensiones, es decir, compuestas de filas y columnas. He aquí un ejemplo de una matriz de dos filas y dos columnas:

 

 

Esta es, precisamente, una de las matrices que se utilizan para obtener los puntos de la figura transformada. Cada elemento de la matriz se define por su posición, así, por ejemplo, m(1,2) es el elemento situado en la primera fila y segunda columna. Ésta es una matriz de orden (2,2), que significa que tiene dos filas y dos columnas. Las matrices cuyo número de filas es igual al número de columnas se llaman matrices cuadradas de orden n, por consiguiente nos podemos referir a esta matriz como una matriz cuadrada de orden 2.

 

La diferencia entre los arrays y las matrices radica en que sobre la matriz se definen determinadas propiedades y operaciones que la convierten en un array especial. Expondremos a continuación las operaciones que se aplican en el mapeado de puntos, que, como ya hemos dicho, son la suma y el producto de matrices.

 

Operaciones con matrices

 

Suma de matrices

Sólo se pueden sumar matrices del mismo orden. La matriz suma es otra matriz del mismo orden que se obtiene sumando los elementos de las dos matrices situados en la misma posición. Por ejemplo:

 

 

Aunque, como veremos más adelante, el objeto Matrix realiza todas las operaciones que necesitemos por nosotros, vamos a escribir, a título meramente ilustrativo, una función que halle la suma de dos matrices. Le entregaremos un par de arrays de tipo Decimal y de la misma dimensión que harán de matrices, y devolverá otro array cuyos elementos serán la matriz suma:

 

Public Function Suma(ByVal Matriz1(,) As Decimal, ByVal Matriz2(,) As Decimal) As Decimal(,)

            'Comprobamos que las dos matrices son del mismo orden,

            'es decir, que las dos tienen igual número de filas

            'e igual número de columnas

If Not Matriz1.GetUpperBound(0) = Matriz2.GetUpperBound(0) AndAlso _

Matriz1.GetUpperBound(1) = Matriz2.GetUpperBound(1) Then Exit Function

 

            'Creamos la matriz suma

Dim MatrizSuma(Matriz1.GetUpperBound(0), Matriz1.GetUpperBound(1)) As Decimal

 

            Dim i, j As Short

            'Y sumamos las matrices

            For i = 0 To Matriz1.GetUpperBound(0)

                For j = 0 To Matriz1.GetUpperBound(1)

                    MatrizSuma(i, j) = Matriz1(i, j) + Matriz2(i, j)

                Next

            Next

 

            Return MatrizSuma

End Function

 

Podemos utilizar este procedimiento para porbar la función:

 

Private Sub Prueba_de_Suma()

        Dim A As Array = New Decimal(,) {{2, 3}, {4, 1}}

        Dim B As Array = New Decimal(,) {{1, 2}, {3, 4}}

        Dim R As Decimal(,) = Suma(A, B)

 

MessageBox.Show(String.Format("{0} {1}" _

& ControlChars.CrLf & "{2} {3}", _

R(0, 0), R(0, 1), R(1, 0), R(1, 1)))

 

End Sub

 

El resultado es la misma matriz que obtuvimos arriba.

 

 

Producto de matrices

 

Sólo se pueden multiplicar dos matrices si el número de columnas de la primera coincide con el número de filas de la segunda. El resultado es una matriz de tantas filas como tiene la primera y tantas columnas la segunda. Además, esta operación no es conmutativa, es decir, el orden de los factores sí altera el producto. Expresado algebraicamente:

 

A(m,n) x B(n,p) = R(m,p)

 

De ello se deduce que para que dos matrices se puedan multiplicar en los dos sentidos, es decir, para que podamos efectuar A x B y B x A, las matrices deben ser cuadradas. Ahora bien, la matriz resultante en cada caso sería diferente, porque no es lo mismo A x B que B x A. Esto, como veremos más adelante, tiene una importancia crucial en GDI+, y veremos que variando el orden en que multipliquemos las matrices obtendremos transformaciones diferentes.

 

Los elementos de la matriz producto R no se calculan multiplicando los correspondientes de las matrices A y B, como es el caso de la suma. Requiere alguna cuenta más. Hemos preparado un pequeño truco fácil de seguir que, además de calcular el producto de matrices, nos servirá de guía para escribir, al igual que hemos hecho en el apartado anterior, una función en Visual Basic que, dadas dos matrices cualesquiera que cumplan los requisitos arriba expuestos, nos calculará su matriz producto.

 

Vamos a multiplicar las siguientes matrices  A x B y obtendremos la matriz R, cuyo número de filas es igual al número de filas de A (2) y cuyo número de columnas es igual al número de columnas de B (2). Asímismo, el número de filas de A coincide con el de columnas de B (2). Puesto que vamos a basarnos en este truco para escribir una función en Visual Basic, numeraremos los índices de cada elemento en base 0.

 

 

El truco siguiente halla uno a uno los elementos de la matriz producto R.

Hallaremos, para ejemplificarlo, el elemento R(1,0).

 

Primero escribirmos:

 

A( , ) * B( , )

 

Y lo repetimos tantas veces como el número de columnas de la matriz A = número de filas de la matriz B = 3

 

A( , ) * B( , )

A( , ) * B( , )

A( , ) * B( , )

 

El primer índice del elemento que estamos hallando R(1,0) será el primer índice de todos los elementos de A, y el segundo índice R(1,0) será el segundo índice de todos los elementos B:

 

A(1, ) * B( ,0)

A(1, ) * B( ,0)

A(1, ) * B( ,0)

 

Añadimos los índices que faltan empezando por cero (o por 1, si hubiésemos definido los índices en base 1) e incrementándolos en una unidad de arriba a abajo:

 

A(1,0) * B(0 ,0)

A(1,1) * B(1 ,0)

A(1,2) * B(2 ,0)

 

Localizamos el valor de cada elemento en las matrices A y B, efectuamos las multiplicaciones y sumamos los resultados:

 

A(1,0) * B(0 ,0) = 3 * 1 = 3

A(1,1) * B(1 ,0) = 2 * 3 = 6

A(1,2) * B(2 ,0) = 1 * 2 = 2

 

R(1,0) = 3 + 6 + 2 = 11

 

Aplicando este sencillo truco a cada uno de los elementos de R obtenemos la matriz producto R = A x B:

 

 

He aquí la función:

 

Public Function Producto(ByVal A(,) As Decimal, ByVal B(,) As Decimal) As Decimal(,)

 

            'Comprobamos que las matrices cumplen los requisitos

            If A.GetUpperBound(1) <> B.GetUpperBound(0) Then

                 Exit Function

            End If

 

            Dim i, j, k As Short

            'Creamos la matriz producto

            Dim R(A.GetUpperBound(0), B.GetUpperBound(1)) As Decimal

 

            'Este array de dos columnas

            'es meramente operativo

            'equivale a las dos columnas

            'que hemos creado en el ejemplo:

            'A(1,0) * B(0 ,0)

            'A(1,1) * B(1 ,0)

            'A(1,2) * B(2 ,0)

            Dim T(A.GetUpperBound(1), 1) As Decimal

 

            'Los dos primeros bucles For

            'sirven para ir visitando

            'todas las posiciones de la matriz R

            For i = 0 To R.GetUpperBound(0)

                For j = 0 To R.GetUpperBound(1)

 

                    'i contiene el primer índice de R

                    'que colocamos en todas las posiciones

                    'A(i, ) de los elementos de la primera matriz

                    'A la vez, en la segunda posción A( ,k)

                    'vamos aumentando el índice desde cero

                    'hasta el número de columnas de A,

                    'que coincide con el de filas de B

                    'igual que hemos hecho en el ejemplo

                    For k = 0 To A.GetUpperBound(1)

                        T(k, 0) = A(i, k)

                    Next

 

                    'j contiene el segundo índice de R

                    'que colocamos en todas las posiciones

                    'B(,j) de los elementos de la segunda matriz

                    'A la vez, en la primera posción B(k, )

                    'vamos aumentando el índice desde cero

                    'hasta el número de filas de B,

                    'que coincide con el de columnas de A

                    'igual que hemos hecho en el ejemplo

                    For k = 0 To B.GetUpperBound(0)

                        T(k, 1) = B(k, j)

                    Next

 

                    'Ya tenemos definidos todos los elementos.

                    'Multiplicamos cada pareja del array T

                    'y vamos acumulando los resultados.

                    'El resultado final lo colocamos en su posición

                    'en la matriz producto R

                    'exactamente igual que en el ejemplo

                    For k = 0 To T.GetUpperBound(0)

                        R(i, j) += T(k, 0) * T(k, 1)

                    Next

 

                Next

            Next

 

            Return R

 

End Function

 

Pruebe el lector a multiplicar las matrices del ejemplo:

 

Private Sub Prueba_de_producto()

        Dim A(,) As Decimal = {{1, 2, 3}, {3, 2, 1}}

        Dim B(,) As Decimal = {{1, 2}, {3, 1}, {2, 1}}

 

        Dim R(,) As Decimal = Producto(A, B)

 

MessageBox.Show(String.Format("{0} {1}" _

& ControlChars.CrLf & "{2} {3}", _

R(0, 0), R(0, 1), R(1, 0), R(1, 1)))

 

End Sub

 

Tipos de matrices

 

Ahora que sabemos operar con matrices, vamos a tipificar las matrices que generan las tres transformaciones básicas (traslación, rotación y escalado) y otras para las que VSNET no ofrece implementación ad hoc.

 

El método que genera transformaciones multiplicando todos los puntos de la figura origen por la matriz que recibe como parámetro se llama Transform(Matrix). El constructor de la clase Matrix espera 6 parámetros: cuatro de ellos representan la matriz cuadrada que multiplicada por todos los puntos de la figura origen genera diversos tipos de transformaciones, incluida la rotación que acabamos de ver, y los otros dos representan la matriz de una fila y dos columnas que sumada a todos los puntos de la figura origen genera, como también hemos visto más arriba, la figura trasladada. Incluimos en su lugar dentro de las matrices los nombres de los parámetros para que el lector los identifique

 

Matriz generadora de transformaciones multiplicando por ella los puntos de la figura origen:

 

Matriz que sumada a cada punto de la figura origen genera su traslación: [dx, dy]

 

La sintáxis del constructor del objeto Matrix es la siguiente:

 

New Matrix(m11, m12, m21, m22, dx, dy)

 

 

Traslación

Se obtiene sumando a cada punto una matriz, obviamente compuesta de una fila y

dos columnas:

 

Punto: [x, y]

Matriz de traslación: [dx, dy]

 

Matriz final: [x, y] + [dx, dy] = [x+dx, y+dy]

 

El valor dx representa el movimiento por el eje de abcisas X y dy el movimiento por

el eje de ordenadas Y.

 

En el apartado anterior hemos visto que el objeto Matrix incluye la matriz de traslación [dx, dy]. Si queremos, por tanto, aplicar una sólo traslación mediante matrices, los elementos mxx han de representar la matriz identidad, que explicamos a continuación. Por ejemplo, si queremos mover una figura 15 píxeles por el eje X y 10 por el eje Y, la línea quedaría así: ("Trayecto" representa un Graphicspath)

 

Trayecto.Transform(New Matrix(1, 0, 0, 1, 15, 10))

 

Identidad

Transformación identidad es aquella que genera una figura exactamente igual, en la

misma posición, tamaño y orientación que la original, y se obtiene entregando

como parámetro al método Transform la matriz identidad:

 

Rotación

Consiste en el giro de los ejes de coordenadas. Se genera entregando al método

Transform la matriz siguiente:

 

donde a representa los grados de giro medidos en radianes. Los radianes miden la

longitud del arco cortado por los dos radios de longitud = 1 que distan entre sí los

grados especificados. Como la circunferencia mide 2pr y r=1, entonces 360º = 2p

radiantes, o lo que es lo mismo, 180º son p radiantes, y por tanto 1 grado =

p/180 radianes, de modo que si queremos expresar la matriz en grados, para así

utilizar la misma unidad de medida que entregábamos al método RotateTransform,

la matriz de rotación nos queda así:

 

 

Aplicando esta matriz obtenemos una figura girada g grados en el sentido de las

agujas del reloj.

 

A continuación presentamos un ejemplo:

 

Private Sub Rotación_con_y_sin_matriz()

Dim i As Short

Dim Lienzo As Graphics = Me.CreateGraphics

 

'Movemos el eje de coordenadas a un lugar cercano al centro

del formulario

Lienzo.TranslateTransform(Me.ClientSize.Width / 2, _

Me.ClientSize.Height / 2 - 100)

 

'Medidas del rectángulo

Dim Esquina As New Point(100, 0)

Dim Tamaño As New Size(150, 50)

 

'Dibujamos una línea desde el origen de coordenadas

'hasta el vértice superior izquierdo del rectángulo

Dim Trayecto1 As New GraphicsPath()

Trayecto1.AddLine(New Point(0, 0), Esquina)

Dim Lápiz1 As New Pen(Color.LightGreen, 8)

Lápiz1.EndCap = LineCap.ArrowAnchor

Lienzo.DrawPath(Lápiz1, Trayecto1)

 

'Dibujamos el rectángulo

Dim Trayecto2 As New GraphicsPath()

Trayecto2.AddRectangle(New Rectangle(Esquina, Tamaño))

Lienzo.FillPath(Brushes.Blue, Trayecto2)

 

'Construimos la matriz. Queremos rotar el rectángulo 90 grados

'y no queremos trasladar la figura, por eso

'los valores dx y dy son cero.

Dim D As Single = 90.0R * PI / 180

Dim Matriz As Matrix = New Matrix(Cos(D), Sin(D), -Sin(D),

Cos(D), 0.0F, 0.0F)

'Rotar el objeto Graphics mediante el método RotateTransform

...

Lienzo.RotateTransform(90) 'Inhabilita esta línea si quieres ver

'los métodos Trayecto1.Transform(M) y Trayecto2.Transform(M)

en acción

'... es lo mismo que entregar al método Tranform de los

GraphicPath

'la matriz M arriba definida

Trayecto1.Transform(Matriz) 'Inhabilita estas dos líneas si

quieres

Trayecto2.Transform(Matriz) 'ver el método

Lienzo.RotateTransform(90) en acción

 

'Dibujamos las figuras encapsuladas en los dos GraphicsPath.

'No hay que perder de vista que toda transformación se ejecuta

sobre

'el eje de coordenadas, no sobre las figuras mismas.

'Por tanto, tras transformar los ejes de coordenadas,

'debemos dibujar "manualmente" las figuras.

Lienzo.DrawPath(Lápiz1, Trayecto1)

Lienzo.FillPath(Brushes.Blue, Trayecto2)

 

Trayecto1.Dispose()

Trayecto2.Dispose()

Lienzo.Dispose()

End Sub

 

 

 

El ejemplo siguiente genera esta misma transformación “manualmente” utilizando

nuestra función de multiplicación de matrices. Multiplicaremos cada uno de los

cuatro vértices del rectángulo por la matriz, y el resultado serán sido los cuatro

vértices del rectángulo girado 90º que vemos en el dibujo. De hecho, todas las

tranformaciones expuestas en este capítulo pueden simularse manualmente

utilizando nuestras funciones Suma y Producto de matrices.

 

Private Sub Rotación_manual_Click()

Dim i As Short

Dim Lienzo As Graphics = Me.CreateGraphics

 

'Movemos el eje de coordenadas a un lugar cercano al centro

del formulario

Lienzo.TranslateTransform(Me.ClientSize.Width / 2, _

Me.ClientSize.Height / 2 - 100)

 

'Medidas del rectángulo

Dim Esquina As New Point(100, 0)

Dim Tamaño As New Size(150, 50)

 

'Dibujamos una línea desde el origen de coordenadas

'hasta el vértice superior izquierdo del rectángulo

Dim Lápiz1 As New Pen(Color.LightGreen, 8)

Lápiz1.EndCap = LineCap.ArrowAnchor

Lienzo.DrawLine(Lápiz1, New Point(0, 0), Esquina)

 

'Dibujamos el rectángulo original

Lienzo.FillRectangle(Brushes.Blue, New Rectangle(Esquina,

Tamaño))

 

'Hallamos los vértices del rectángulo

Dim N1 As Point = Esquina 'Noroeste

Dim N2 As Point = New Point(N1.X + Tamaño.Width, N1.Y)

'Noreste

Dim N3 As Point = New Point(N2.X, N2.Y + Tamaño.Height)

'Sureste

Dim N4 As Point = New Point(N1.X, N3.Y) 'Suroeste

 

'Construimos la matriz de rotación.

'Queremos rotar el rectángulo 90 grados

Dim D As Single = 90.0R * PI / 180

Dim Matriz As Array = New Decimal(,) {{Cos(D), Sin(D)}, {-

Sin(D), Cos(D)}}

 

'Hallamos los vértices del rectángulo transformado

'Primero hallamos las matrices producto

Dim K1(,) As Decimal = Producto(New Decimal(,) {{N1.X, N1.Y}},

Matriz)

Dim K2(,) As Decimal = Producto(New Decimal(,) {{N2.X, N2.Y}},

Matriz)

Dim K3(,) As Decimal = Producto(New Decimal(,) {{N3.X, N3.Y}},

Matriz)

Dim K4(,) As Decimal = Producto(New Decimal(,) {{N4.X, N4.Y}},

Matriz)

 

'y luego las convertimos en puntos

Dim T1 As Point = New Point(K1(0, 0), K1(0, 1))

Dim T2 As Point = New Point(K2(0, 0), K2(0, 1))

Dim T3 As Point = New Point(K3(0, 0), K3(0, 1))

Dim T4 As Point = New Point(K4(0, 0), K4(0, 1))

 

'y por fin dibujamos el rectángulo transformado "a mano"

Lienzo.FillPolygon(Brushes.Blue, New Point() {T1, T2, T3, T4})

'y la flecha

Lienzo.DrawLine(Lápiz1, New Point(0, 0), T1)

Lienzo.Dispose()

‘El resultado es exactamente el mismo obtenido con la función anterior

End Sub

 

Deformación

La deformación ocurre cuando giramos el eje X k grados y el eje Y l grados y k

<> l. Es decir, cada eje gira independientemente del otro. El ángulo que forma

cada brazo del eje con su contiguo deja de ser necesariamente de noventa grados,

y por tanto los ángulos de la figura se alteran. Esta es la matriz:

 

 

Donde k son los grados que gira el eje X y l los gradpos que gira el eje Y.

 

Veamos un ejemplo:

 

Private Sub Deformación()

Dim Lienzo As Graphics = Me.CreateGraphics

'Trasladamos el origen de coordenadas

'cerca del centro del formulario

Lienzo.TranslateTransform(Me.ClientSize.Width / 2 - 50, _

Me.ClientSize.Height / 2 - 100)

 

'Dibujamos los ejes de coordenadas

Dim h As Integer = 350

Dim lápiz As New Pen(Color.Black, 2)

lápiz.DashStyle = DashStyle.Dot

Lienzo.DrawLine(lápiz, -h, 0, h, 0)

Lienzo.DrawLine(lápiz, 0, h, 0, -h)

 

'Dibujamos un rectángulo

Dim Trayecto1 As New GraphicsPath()

Trayecto1.AddRectangle(New Rectangle(0, 0, 250, 100))

Lienzo.FillPath(Brushes.LightGreen, Trayecto1)

 

'Creamos la matriz de transformación

'el eje X lo giramos 5 grados

'y el eje Y lo giramos 60 grados

Dim GiroX As Integer = 5

Dim GiroY As Integer = 60

Dim Radián As Decimal = PI / 180

Dim R As Matrix = New _

Matrix(Cos(GiroX * Rad), Sin(GiroX * Rad), _

-Sin(GiroY * Radián), Cos(GiroY * Radián), _

0.0F, 0.0F)

 

'Ejecutamos la transformación

'y dibujamos el rectángulo transformado

Trayecto1.Transform(R)

Lienzo.FillPath(Brushes.Blue, Trayecto1)

End Sub

 

En el dibujo que genera este código podemos apreciar la alteración de los ángulos

de la figura causada por la diferencia de giro de cada eje de coordenadas:

 

 

Simetría

Si cambiamos ceros por unos y viceversa en la matriz identidad obtenemos la

figura reflejada en un espejo colocado sobre la bisectriz de los ejes de coordenadas,

es decir, sobre la línea que divide en dos partes iguales la superficie delimitada por

los ejes +X y +Y. Lo veremos más claro en el ejemplo:

 

 

Private Sub Simetría()

Dim i As Short

Dim Lienzo As Graphics = Me.CreateGraphics

 

'Medidas del rectángulo

Dim Esquina As New Point(250, 50)

Dim Tamaño As New Size(250, 100)

 

'Dibujamos la bisectriz

Dim Lápiz As New Pen(Color.Black, 4)

Lápiz.DashStyle = DashStyle.Dot

Lienzo.DrawLine(Lápiz, 0, 0, Me.ClientSize.Height, _

Me.ClientSize.Height)

 

'Dibujamos el rectángulo

Dim Trayecto1 As New GraphicsPath()

Trayecto1.AddRectangle(New Rectangle(Esquina, Tamaño))

Lienzo.FillPath(Brushes.Blue, Trayecto1)

 

'Construimos la matriz de simetría.

Dim Matriz As Matrix = _

New Matrix(0.0F, 1.0F, 1.0F, 0.0F, 0.0F, 0.0F)

 

'Ejecutamos la transformación

Trayecto1.Transform(Matriz)

 

'y dibujamos el rectángulo transformado

Lienzo.FillPath(Brushes.Blue, Trayecto1)

 

Trayecto1.Dispose()

Lienzo.Dispose()

End Sub

 

 

 

Si unimos un vértice de un rectángulo con la bisectriz formando una línea

perpendicular y hacemos lo mismo con el mismo vértice del rectángulo reflejado,

entonces las dos líneas se encuentran formando una sola, y la distancia entre el

vértice y la bisectriz es la misma para las dos.

 

Escalado

Como ya sabemos, el efecto del escalado es el aumento o disminución del tamaño

de la figura por los ejes X y/o Y. La matriz de escalado es la siguiente:

 

 

X = número de veces que escalamos el eje X

Y = número de veces que escalamos el eje Y

 

Es lo mismo, por ejemplo,

 

ScaleTransform(3.0F, 2.0F)

 

Que

 

Transform(New Matrix(3.0F, 0.0F, 0.0F, 2.0F, 0.0F, 0.0F)

 

Escalado de figura simétrica

 

 

Es decir, los valores de escalado X e Y los colocamos en el lugar que ocupan tras

convertir la matriz identidad en simétrica. El lector puede fácilmente probar esta

matriz sustituyendo la línea

 

Dim Matriz As Matrix = New Matrix(0.0F, 1.0F, 1.0F, 0.0F, 0.0F, 0.0F)

 

Del ejemplo de las figuras simétricas por, por ejemplo, esta:

 

Dim Matriz As Matrix = New Matrix(0.0F, 0.5F, 0.5F, 0.0F, 0.0F, 0.0F)

 

Que reduce el tamaño del rectángulo simétrico a la mitad.

 

 

Combinación de transformaciones

 

Otra consecuencia del hecho de que las transformaciones se aplican al eje de

coordenadas y no al gráfico es que cada transformación se acumula a la anterior.

Dos son los tipos posibles de acumulación: suma y multiplicación.

Traslación con el resto de transformaciones

 

Como vimos más arriba, la traslación se calcula sumando a la matriz punto [x, y] la

matriz de traslación [dx, dy] (VSNET llama a esta matriz “Offset”), resultando

=[x+dx, y+dy]. Puesto que la suma de matrices es conmutativa, es decir, M + N =

N + M, no es imprescindible un objeto específico para la matriz [dx, dy]. De hecho,

y dado que, además, es indiferente ejecutar primero la transformación y después la

traslación o primero la traslación y después la transformación, dx y dy forman

parte, como vimos más arriba, de los elementos del objeto Matrix. Así resulta que

Matrix, en realidad, contiene dos matrices, y es capaz de ejecutar las dos

transformaciones (traslación y cualquier otra) a la vez. Todo lo que tenemos que

hacer es, como venimos haciendo en los ejemplos, dar valores a los elementos

m11, m12, m21, m22 pertenecientes a la matriz de transformación y a los

elementos dx y dy pertenecientes a la matriz de traslación. Se efectuarán las dos

transformaciones.

 

Ahora bien, si queremos que la figura se mueva y no ejecute

ninguna otra transformación, entonces los valores de m11, m12, m21 y m22 deben

formar la matriz identidad. Por ejemplo:

 

Trayecto1.Transform(New Matrix(1, 0, 0, 1, 100, 50))

 

Mueve la figura 100 pixels por el eje de abcisas y 50 por el de ordenadas sin

modificarla. Sólo si también damos valores a mXX entonces se ejecutan las dos

transformaciones a la vez.

 

Multiplicación de transformaciones

Si ejecutamos una serie de transformaciones en la misma figura aplicando la

matrices M1, M2, M3, … Mn-1, Mn, entonces podemos evitarnos aplicarlas todas y

aplicar sólo una matriz R obteniendo el mismo resultado, y la hallaríamos

multiplicando todas en el mismo orden: R = M1 x M2 x M3 x … x Mn-1 x Mn. Para ello

utilizamos el método Multiply(Matrix, MatrixOrder), como vemos en el siguiente

ejemplo:

 

Private Sub Combinación()

Dim Lienzo As Graphics = Me.CreateGraphics

 

'Este procedimiento dibuja (casi) todos los pasos

'de una serie de transformaciones

'aplicadas a un GraphicsPath

'Movemos el eje de coordenadas al centro del formulario

Lienzo.TranslateTransform(Me.ClientSize.Width / 2,

Me.ClientSize.Height / 2)

 

'Dibujamos un círculo en el centro

'por pura estética

Dim Trayecto0 As New GraphicsPath()

Dim Lápiz As Pen = New Pen(Color.Violet, 4)

Dim Rectángulo As Rectangle = New Rectangle(-25, -25, 50, 50)

Trayecto0.AddEllipse(Rectángulo)

Lienzo.DrawPath(Lápiz, Trayecto0)

 

'Encapsulamos un cuadrado en un GraphicsPath

'pero no lo dibujamos todavía

Dim Trayecto1 As New GraphicsPath()

Trayecto1.AddRectangle(Rec)

 

'antes de dibujarlo lo transformamos:

'multiplicamos por dos su tamaño

Trayecto1.Transform(New Matrix(2, 0, 0, 2, 0, 0))

 

'y lo giramos 45 grados

Dim Ángulo As Decimal = 45 * PI / 180

Trayecto1.Transform(New _

Matrix(Cos(Ángulo), Sin(Ángulo), -Sin(Ángulo), Cos(Ángulo), 0, 0))

Lienzo.DrawPath(Lápiz, Trayecto1) 'ahora sí lo dibujamos

 

Dim i As Short : For i = 0 To 3

'Aplicamos cuatro transformaciones seguidas

'con la misma matriz

Trayecto1.Transform(New Matrix(1.5, 0, 0, 1, 0, 0))

Lienzo.DrawPath(Lápiz, Trayecto1)

Next

 

Trayecto0.Dispose()

Trayecto1.Dispose()

Lienzo.Dispose()

End Sub

 

Private Sub Multiplicación()

'Este procedimiento ejecuta

'las mismas transformaciones que el anterior

'pero sólo dibuja el resultado final.

'En vez de ir aplicando todas las matrices una a una

'las multiplica, y sólo aplica la matriz producto

 

Dim Ángulo As Decimal = 45 * PI / 180

Dim M1 As New Matrix(2, 0, 0, 2, 0, 0)

Dim M2 As New _

Matrix(Cos(Ángulo), Sin(Ángulo), -Sin(Ángulo), Cos(Ángulo), 0, 0)

Dim M3 As New Matrix(1.5, 0, 0, 1, 0, 0)

 

M1.Multiply(M2, MatrixOrder.Append)

'M1.Multiply(M2, MatrixOrder.Append) = M1 * M2

'M1.Multiply(M2, MatrixOrder.Prepend) = M2 * M1

 

Dim i As Short : For i = 0 To 3

'Multiply no es una función,

'no devuleve la matriz producto

'sino que sustituye los valores de M1

'por el resultado de la operación

'es decir: M1 = M1 * M3

M1.Multiply(M3, MatrixOrder.Append)

Next

 

Dim Lienzo As Graphics = Me.CreateGraphics

'Movemos el eje de coordenadas al centro del formulario

Lienzo.TranslateTransform(Me.ClientSize.Width / 2,

Me.ClientSize.Height / 2)

 

Dim Trayecto0 As New GraphicsPath()

Dim Rectángulo As Rectangle = New Rectangle(-25, -25, 50, 50)

Trayecto0.AddEllipse(Rectángulo) 'dibujamos un circulito por

mera estética

 

Dim Lápiz As Pen = New Pen(Color.Blue, 4)

Lienzo.DrawPath(Lápiz, Trayecto0)

Dim Trayecto1 As New GraphicsPath()

Trayecto1.AddRectangle(Rectángulo)

 

'ejecutamos sólo una transformación

'aplicando la matriz obtenida de multiplicar

'M1 * M2 * M3 * M3 * M3 * M3

Trayecto1.Transform(M1)

Lienzo.DrawPath(Lápiz, Trayecto1)

 

Trayecto0.Dispose()

Trayecto1.Dispose()

Lienzo.Dispose()

End Sub

 

Mostramos en primer lugar el dibujo generado por el primer procedimiento y

después el generado por el segundo, y por último mostramos los dos dibujos

superpuestos, donde se ve claramente que la matriz producto de toda la serie de

matrices implicadas genera el mismo dibujo que la última matriz de la serie:

 


Índice del curso GDI+
 

la Luna del Guille o... el Guille que está en la Luna... tanto monta...