Tutorial de Canvas:Transformaciones
De MDC
Esta página está traduciéndose a partir del artículo Canvas_tutorial:Transformations, razón por la cual puede haber algunos errores sintácticos o partes sin traducir. Puedes colaborar continuando con la traducción
Tabla de contenidos |
[editar] Grabado y Restauración de "estados"
Antes de estudiar los métodos de transformación, vamos a introducir otros dos métodos que son indispensables una vez se comienza a generar dibujos o diseños más complejos.
save()
restore()
Los métodos save y restore de un marco se utilizan para grabar y restaurar el "estado" de un marco (el "estado" es el aspecto visual que tiene el marco). El estado de dibujo o diseño del marco es, básicamente, una imagen instantánea de todos los estilos y transformaciones que le han sido aplicadas. Ambos métodos carecen de parámetros.
Los "estados" de un marco se almacenan en una pila. Cada vez que se invoca el método save, se pasa el estado actual del dibujo a la pila. El "estado" de un dibujo viene determinado por:
- Las transformaciones que le han sido aplicadas (por ejemplo: traslación, rotación y escala - véase más adelante).
- Los valores de las propiedades
strokeStyle,fillStyle,globalAlpha,lineWidth,lineCap,lineJoin,miterLimit,shadowOffsetX,shadowOffsetY,shadowBlur,shadowColor,globalCompositeOperation. - Las características de vía de ajuste (clipping path) actuales, que analizaremos en la siguiente sección.
Puede invocar el método save tantas veces como lo desee.
Cada vez que se invoca el método restore, la pila devuelve el último "estado" grabado y se recuperan todas las características que estaban grabadas en dicho "estado".
[editar] Ejemplo de grabación y recuperación del "estado" de un marco
Este ejemplo trata de ilustrar cómo funciona la pila de "estados" de un dibujo o diseño, dibujando un conjunto de rectángulos consecutivos.El primer paso consiste en dibujar un rectángulo grandee con los parámetros iniciales o "por defecto". Seguidamente se graba este "estado" y se hacen modificaciones en el color de relleno. Después se dibuja un segundo rectángulo azul, más pequeño, y grabamos de nuevo el "estado". Cambiamos otra vez algunos parámetros y dibujamos el tercer rectángulo blanco semi-transparente.
Hasta ahora esto es muy similar a lo que hemos hecho en las secciones previas. No obstante, una vez invocamos la primera instrucción restore, se recupera el último "estado" situado en la parte superior de la pila y se restauran los parámetros de dicho estado anterior. Si no hubiéramos grabado el "estado" usando el método save, tendríamos que cambiar el color de llenado y la opción de transparencia manualmente para poder volver al estado anterior. Esto sería muy fácil para dos propiedades, pero a medida que tuviéramos más parámetros nuestro código a rehacer se alargaría muy rápidamente.
Cuando se invoca por segunda vez el método restore, se restaura el estado original (aquel que teníamos antes de invocar por primera vez el método save) y el último rectángulo aparece de nuevo dibujado en negro.
function draw() {
var ctx = document.getElementById('canvas').getContext('2d');
ctx.fillRect(0,0,150,150); // Dibuja un rectángulo con los parámetros por defecto
ctx.save(); // Graba el estado por defecto
ctx.fillStyle = '#09F' // Haz cambios a los parámetros
ctx.fillRect(15,15,120,120); // Dibuja un rectángulo con los nuevos parámetros
ctx.save(); // Graba el estado actual
ctx.fillStyle = '#FFF' // Haz cambios a los parámetros
ctx.globalAlpha = 0.5;
ctx.fillRect(30,30,90,90); // Dibuja un rectángulo con los nuevos parámetros
ctx.restore(); // Restaura el estado anterior
ctx.fillRect(45,45,60,60); // Dibuja un rectángulo con los parámetros restaurados
ctx.restore(); // Restaura el estado original
ctx.fillRect(60,60,30,30); // Dibuja un rectángulo con los parámetros restaurados
}
[editar] Traslación
El primero de los métodos de transformación que estudiaremos es el métodotranslate. Este método se utiliza para mover un marco y su origen a un nuevo punto de la cuadrícula del espacio de trabajo ("grid" en inglés).
translate(x, y)
Este método requiere tomar dos argumentos. x que es el parámetro que define el movimiento horizontal a izquierda o derecha del marco, e y que es el parámetro que define el movimiento vertical hacia arriba o abajo del marco (ilustrado por la imagen situada a la derecha).
Siempre es una buena idea grabar el "estado" del marco antes de realizar cualquier transformación. En la mayoría de los casos, es más fácil invocar el método restore que volver a tener que deshacer la traslación para volver al estado original. Además, si usted está trasladando dentro de un bucle de código y no graba el "estado" del marco antes del bucle, podría terminar perdiendo parte de su dibujo, porque éste, al trasladarse, se dibujara fuera de los límites del espacio de trabajo.
[editar] Ejemplo del método translate
Este ejemplo demuestra algunos de los beneficios de trasladar el origen de un marco. He realizado una función drawSpirograph, que llamaré "espirógrafo", para realizar dibujos espirales. Éstos se dibujan alrededor de un origen. Si no utilizara la función translate, Sólo vería una cuarta parte del dibujo en el marco. El método translate también me proporciona la libertad de colocar el dibujo en cualquier parte del marco sin tener que ajustar las coordenadas manualmente en la función "espirógrafo". Este ejemplo hace que el método sea más fácil de entender y utilizar.
En la función draw, llamo a la función drawSpirograph nueve veces usando dos bucles for. En cada bucle, el marco se traslada, se dibuja el "espirógrafo" y el marco se vuelve a desplazar de nuevo a su posición original.
function draw() {
var ctx = document.getElementById('canvas').getContext('2d');
ctx.fillRect(0,0,300,300);
for (var i=0;i<3;i++) {
for (var j=0;j<3;j++) {
ctx.save();
ctx.strokeStyle = "#9CFF00";
ctx.translate(50+j*100,50+i*100);
drawSpirograph(ctx,20*(j+2)/(j+1),-8*(i+3)/(i+1),10);
ctx.restore();
}
}
}
function drawSpirograph(ctx,R,r,O){
var x1 = R-O;
var y1 = 0;
var i = 1;
ctx.beginPath();
ctx.moveTo(x1,y1);
do {
if (i>20000) break;
var x2 = (R+r)*Math.cos(i*Math.PI/72) - (r+O)*Math.cos(((R+r)/r)*(i*Math.PI/72))
var y2 = (R+r)*Math.sin(i*Math.PI/72) - (r+O)*Math.sin(((R+r)/r)*(i*Math.PI/72))
ctx.lineTo(x2,y2);
x1 = x2;
y1 = y2;
i++;
} while (x2 != R-O && y2 != 0 );
ctx.stroke();
}
[editar] Rotación
El segundo método de transformación es el métodorotate. Se utiliza para rotar el marco alrededor de su origen actual.
rotate(angle)
Este método sólo precisa tomar un parámetro, que es el ángulo de rotación que se aplicará al marco. Este parámetro es la rotación en el sentido de las agujas del reloj, medido en radianes (ilustrado en la imagen situada a la derecha).
El punto central de la rotación es siempre el origen del marco. Para cambiar el punto de rotación tendremos que desplazar el marco utilizando el método translate.
[editar] Ejemplo del método rotate
En el ejemplo, que puede verse a la derecha, he utilizado el método rotate para dibujar formas siguiendo un patrón circular. Usted también podría haber calculado las coordenadas individuales x e y mediante las fórmulas: (x = r*Math.cos(a); y = r*Math.sin(a)). En este caso concreto no importa realmente qué método elija, porque estamos dibujando círculos. Mediante el cálculo de las coordenadas sólo se consigue rotar las posiciones centrales de los círculos y no los círculos en sí mismos, mientras que la utilización del método rotate tiene como resultado ambos cambios, pero, por supuesto, los círculos no cambian de aspecto independientemente de cuánto se roten em torno a sus centros.
De nuevo tenemos dos bucles. El primero determina el número de anillos y el segundo determina el número de puntos que se dibujan en cada anillo. Antes de dibujar cada anillo, grabo el "estado" del marco, de manera que pueda recuperarlo fácilmente. Por cada punto que se dibuja, hago una rotación de las coordenadas del marco en función de un ángulo que viene determinado por el número de puntos del anillo. El círculo más interno Ttiene 6 puntos, luego en cada paso hago una rotación sobre un ángulo de 360/6 = 60 grados. Con cada anillo adicional, el número de puntos se dobla y, en consecuencia, el ángulo se reduce a la mitad.
function draw() {
var ctx = document.getElementById('canvas').getContext('2d');
ctx.translate(75,75);
for (i=1;i<6;i++){ // Loop through rings (from inside to out)
ctx.save();
ctx.fillStyle = 'rgb('+(51*i)+','+(255-51*i)+',255)';
for (j=0;j<i*6;j++){ // draw individual dots
ctx.rotate(Math.PI*2/(i*6));
ctx.beginPath();
ctx.arc(0,i*12.5,5,0,Math.PI*2,true);
ctx.fill();
}
ctx.restore();
}
}
[editar] Escalado
El siguiente método de transformación es el escalado. Se utiliza para aumentar o dismunuir las unidades del tamaño de nuestro marco. Este método puede usarse para dibujar formas ampliadas o reducidas (como por ejemplo: bitmaps).
scale(x, y)
Este método precisa tomar dos parámetros. x es el factor de escala en la dirección horizontal e y es el factor de escala en la dirección vertical. Ambos parámetros deben ser número positivos.
Los valores menores que 1.0 reducen el tamaño de la unidad y los valores mayores que 1.0 aumentan el tamaño de la unidad. Obviamente, si se establecen los parámetros exactamente a 1.0 el tamaño de la unidad no se ve afectado.
Por defecto, una unidad en el área de trabajo equivale exactamente a un "pixel". Si aplicamos, por ejemplo, un factor de escalado de 0.5, la unidad resultante será 0.5 pixels, de manera que las formas se dibujarán a mitad de su tamaño. De forma análoga, al establecer el factor de escalado a 2.0 el tamaño de la unidad se incrementará a dos pixels, lo cual resultará en que la forma se dibujará al doble de su tamaño.
[editar] Ejemplo del método scale
En este último ejemplo he utilizado la función "espirógrafo", de uno de los ejemplos anteriores, para dibujar nueve formas con diferentes factores de escalado. La figura superior izquierda ha sido dibujada sin aplicarle ningún escalado. La imágenes amarillas situadas a la derecha tienen todas un factor uniforme de escalado (el mismo valor para los parámetros x e y parameters). Si observas el código, más abajo, verás que he utilizado el método scale dos veces con los mismos parámetros de escala para el segundo y tercer "espirógrafo". Dado que no restauré el "estado" del marco, la tercera imagen se dibuja con un factor de escalado de 0.75 × 0.75 = 0.5625.
A la siguiente fila de formas azules se les ha aplicado un escalado no-uniforme en la dirección vertical. Cada una de las formas tiene el factor de escalado x fijado a 1.0, lo que significa que no se les aplica escalado alguno. El factor de escalado y se establece a 0.75, lo que da como resultado las tres formas que se están aplastando. La forma circular original se ha convertido ahora en una elipse. Si observa de cerca verá que el ancho de línea también ha sido reducido en el sentido vertical.
La tercera fila de formas verdes es similar a la de arriba, pero ahora he aplicado un factor de escalado en la dirección horizontal.
function draw() {
var ctx = document.getElementById('canvas').getContext('2d');
ctx.strokeStyle = "#fc0";
ctx.lineWidth = 1.5;
ctx.fillRect(0,0,300,300);
// Escalado uniforme
ctx.save()
ctx.translate(50,50);
drawSpirograph(ctx,22,6,5); // sin escalado
ctx.translate(100,0);
ctx.scale(0.75,0.75);
drawSpirograph(ctx,22,6,5);
ctx.translate(133.333,0);
ctx.scale(0.75,0.75);
drawSpirograph(ctx,22,6,5);
ctx.restore();
// Escalado no-uniforme (en la dirección: y)
ctx.strokeStyle = "#0cf";
ctx.save()
ctx.translate(50,150);
ctx.scale(1,0.75);
drawSpirograph(ctx,22,6,5);
ctx.translate(100,0);
ctx.scale(1,0.75);
drawSpirograph(ctx,22,6,5);
ctx.translate(100,0);
ctx.scale(1,0.75);
drawSpirograph(ctx,22,6,5);
ctx.restore();
// Escalado no-uniforme (en la dirección: x)
ctx.strokeStyle = "#cf0";
ctx.save()
ctx.translate(50,250);
ctx.scale(0.75,1);
drawSpirograph(ctx,22,6,5);
ctx.translate(133.333,0);
ctx.scale(0.75,1);
drawSpirograph(ctx,22,6,5);
ctx.translate(177.777,0);
ctx.scale(0.75,1);
drawSpirograph(ctx,22,6,5);
ctx.restore();
}
[editar] Transformaciones
Los últimos métodos de transformación permiten realizar modificaciones directamente a la matriz de transformación.
transform(m11, m12, m21, m22, dx, dy)
Este método debe multiplicar la matriz actual de transformación por la matriz descrita por:
m11 m21 dx m12 m22 dy 0 0 1
Si cualquiera de los argumentos es "infinito", la matriz de transformación deberá marcarse como "infinita", para evitar que el método produzca una excepción (error).
setTransform(m11, m12, m21, m22, dx, dy)
Este método debe devolver o restaurar (reset) la transformación actual a la matriz de identidad y luego invocar el método transform con los mismos argumentos. Si cualquiera de los argumentos es "infinito", la matriz de transformación deberá marcarse como "infinita", para evitar que el método produzca una excepción (error).
[editar] Ejemplos de los métodos transform / setTransform
function draw() {
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var i = 0;
var sin = Math.sin(Math.PI/6);
var cos = Math.cos(Math.PI/6);
ctx.translate(200, 200);
var c = 0;
for (i; i <= 12; i++) {
c = Math.floor(255 / 12 * i);
ctx.fillStyle = "rgb(" + c + "," + c + "," + c + ")";
ctx.fillRect(0, 0, 100, 10);
ctx.transform(cos, sin, -sin, cos, 0, 0);
}
ctx.setTransform(-1, 0, 0, 1, 200, 200);
ctx.fillStyle = "rgba(255, 128, 255, 0.5)";
ctx.fillRect(0, 50, 100, 100);
}