Professional Documents
Culture Documents
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.
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:
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]
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]
1 0
0 1
Rotación
Consiste en el giro de los ejes de coordenadas. Se genera entregando al método
Transform la matriz siguiente:
cos( ) sin( )
sin( ) cos( )
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í:
cos( * 180 sin( *
180
sin( * cos( *
180 180
Aplicando esta matriz obtenemos una figura girada g grados en el sentido de las
agujas del reloj.
'Dibujamos el rectángulo
Dim Trayecto2 As New GraphicsPath()
Trayecto2.AddRectangle(New Rectangle(Esquina, Tamaño))
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.
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:
cos( * 180 sin( *
180
sin( * cos( *
180 180
Donde k son los grados que gira el eje X y l los gradpos que gira el eje Y.
Veamos un ejemplo:
'Dibujamos un rectángulo
Dim Trayecto1 As New GraphicsPath()
Trayecto1.AddRectangle(New Rectangle(0, 0, 250, 100))
Lienzo.FillPath(Brushes.LightGreen, Trayecto1)
'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:
0 1
1 0
Private Sub Simetría()
Dim i As Short
Dim Lienzo As Graphics = Me.CreateGraphics
'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)
'Ejecutamos la transformación
Trayecto1.Transform(Matriz)
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 0
0 Y
ScaleTransform(3.0F, 2.0F)
Que
Transform(New Matrix(3.0F, 0.0F, 0.0F, 2.0F, 0.0F, 0.0F)
0 X
Y 0
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)
Dim Matriz As Matrix = New Matrix(0.0F, 0.5F, 0.5F, 0.0F, 0.0F, 0.0F)
Combinación 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.
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:
Trayecto0.Dispose()
Trayecto1.Dispose()
Lienzo.Dispose()
End Sub
M1.Multiply(M2, MatrixOrder.Append)
'M1.Multiply(M2, MatrixOrder.Append) = M1 * M2
'M1.Multiply(M2, MatrixOrder.Prepend) = M2 * M1
Trayecto0.Dispose()
Trayecto1.Dispose()
Lienzo.Dispose()
End Sub