Buscar..
Dibujar rápidamente muchas imágenes traducidas, escaladas y rotadas.
Hay muchas situaciones en las que desea dibujar una imagen que se gira, se escala y se traduce. La rotación debe ocurrir alrededor del centro de la imagen. Esta es la forma más rápida de hacerlo en el lienzo 2D. Estas funciones se adaptan bien a los juegos en 2D, donde la expectativa es generar unos cientos de imágenes de hasta más de 1000 imágenes cada 60 segundos. (Depende del hardware)
// assumes that the canvas context is in ctx and in scope
function drawImageRST(image, x, y, scale, rotation){
ctx.setTransform(scale, 0, 0, scale, x, y); // set the scale and translation
ctx.rotate(rotation); // add the rotation
ctx.drawImage(image, -image.width / 2, -image.height / 2); // draw the image offset by half its width and height
}
Una variante también puede incluir el valor alfa que es útil para los sistemas de partículas.
function drawImageRST_Alpha(image, x, y, scale, rotation, alpha){
ctx.setTransform(scale, 0, 0, scale, x, y); // set the scale and translation
ctx.rotate(rotation); // add the rotation
ctx.globalAlpha = alpha;
ctx.drawImage(image, -image.width / 2, -image.height / 2); // draw the image offset by half its width and height
}
Es importante tener en cuenta que ambas funciones dejan el contexto del lienzo en un estado aleatorio. Aunque las funciones no se verán afectadas por otros renderizando mi ser. Cuando haya terminado de representar las imágenes, es posible que deba restaurar la transformación predeterminada
ctx.setTransform(1, 0, 0, 1, 0, 0); // set the context transform back to the default
Si usa la versión alfa (segundo ejemplo) y luego la versión estándar, tendrá que asegurarse de que se restaure el estado alfa global
ctx.globalAlpha = 1;
Un ejemplo del uso de las funciones anteriores para representar algunas partículas y algunas imágenes.
// assume particles to contain an array of particles
for(var i = 0; i < particles.length; i++){
var p = particles[i];
drawImageRST_Alpha(p.image, p.x, p.y, p.scale, p.rot, p.alpha);
// no need to rest the alpha in the loop
}
// you need to reset the alpha as it can be any value
ctx.globalAlpha = 1;
drawImageRST(myImage, 100, 100, 1, 0.5); // draw an image at 100,100
// no need to reset the transform
drawImageRST(myImage, 200, 200, 1, -0.5); // draw an image at 200,200
ctx.setTransform(1,0,0,1,0,0); // reset the transform
Girar una imagen o camino alrededor de su punto central
Los pasos 1 a 5 a continuación permiten que cualquier imagen o forma de trayectoria se mueva a cualquier lugar del lienzo y se gire a cualquier ángulo sin cambiar ninguna de las coordenadas del punto original de la imagen / forma de trayectoria.
Mueva el origen del lienzo [0,0] al punto central de la forma
context.translate( shapeCenterX, shapeCenterY );
Gire el lienzo en el ángulo deseado (en radianes)
context.rotate( radianAngle );
Mueve el origen del lienzo de nuevo a la esquina superior izquierda
context.translate( -shapeCenterX, -shapeCenterY );
Dibuja la imagen o la forma del camino usando sus coordenadas originales.
context.fillRect( shapeX, shapeY, shapeWidth, shapeHeight );
¡Siempre limpia! Establecer el estado de transformación de nuevo a donde estaba antes de # 1
Paso # 5, Opción # 1: Deshacer todas las transformaciones en orden inverso
// undo #3 context.translate( shapeCenterX, shapeCenterY ); // undo #2 context.rotate( -radianAngle ); // undo #1 context.translate( -shapeCenterX, shapeCenterY );
Paso # 5, Opción # 2: Si el lienzo estaba en un estado sin transformar (el valor predeterminado) antes de comenzar el paso # 1, puede deshacer los efectos de los pasos # 1-3 restableciendo todas las transformaciones a su estado predeterminado
// set transformation to the default state (==no transformation applied) context.setTransform(1,0,0,1,0,0)
Código de ejemplo de demostración:
// canvas references & canvas styling
var canvas=document.createElement("canvas");
canvas.style.border='1px solid red';
document.body.appendChild(canvas);
canvas.width=378;
canvas.height=256;
var ctx=canvas.getContext("2d");
ctx.fillStyle='green';
ctx.globalAlpha=0.35;
// define a rectangle to rotate
var rect={ x:100, y:100, width:175, height:50 };
// draw the rectangle unrotated
ctx.fillRect( rect.x, rect.y, rect.width, rect.height );
// draw the rectangle rotated by 45 degrees (==PI/4 radians)
ctx.translate( rect.x+rect.width/2, rect.y+rect.height/2 );
ctx.rotate( Math.PI/4 );
ctx.translate( -rect.x-rect.width/2, -rect.y-rect.height/2 );
ctx.fillRect( rect.x, rect.y, rect.width, rect.height );
Introducción a las transformaciones
Las transformaciones alteran la posición inicial de un punto dado al mover, rotar y escalar ese punto.
- Traducción: Mueve un punto por una
distanceX
y unadistanceY
. - Rotación: Gira un punto en un
radian angle
alrededor de su punto de rotación. El punto de rotación predeterminado en Lienzo HTML es el origen superior izquierdo [x = 0, y = 0] del Lienzo. Pero puedes reposicionar el punto de rotación usando traducciones. - Escalado: escala la posición de un punto mediante un
scalingFactorX
yscalingFactorY
descalingFactorY
desde su punto de escala. El punto de escala predeterminado en el Lienzo HTML es el origen superior izquierdo [x = 0, y = 0] del Lienzo. Pero puedes reposicionar el punto de escala usando traducciones.
También puede hacer transformaciones menos comunes, como cizallamiento (sesgo), configurando directamente la matriz de transformación del lienzo usando context.transform
.
Traducir (== mover) un punto con context.translate(75,25)
Girar un punto con context.rotate(Math.PI/8)
Escala un punto con context.scale(2,2)
Canvas realmente logra transformaciones al alterar todo el sistema de coordenadas del canvas.
-
context.translate
moverá el origen del lienzo [0,0] desde la esquina superior izquierda a una nueva ubicación. -
context.rotate
rotará todo el sistema de coordenadas del lienzo alrededor del origen. -
context.scale
escalará todo el sistema de coordenadas del lienzo alrededor del origen. Piense en esto como un aumento del tamaño de cada x, y en el lienzo:every x*=scaleX
yevery y*=scaleY
.
Las transformaciones del lienzo son persistentes. Todos los dibujos nuevos continuarán transformándose hasta que restablezca la transformación del lienzo a su estado predeterminado (== totalmente sin transformar). Puede restablecer la configuración predeterminada con:
// reset context transformations to the default (untransformed) state
context.setTransform(1,0,0,1,0,0);
Una matriz de transformación para realizar un seguimiento de las formas traducidas, rotadas y escaladas
Canvas le permite context.translate
, context.rotate
y context.scale
para dibujar su forma en la posición y el tamaño que necesita.
Canvas utiliza una matriz de transformación para realizar un seguimiento eficiente de las transformaciones.
- Puedes cambiar la matriz de Canvas con
context.transform
- Puede cambiar la matriz de Canvas con los comandos de
translate, rotate & scale
individuales - Puede sobrescribir completamente la matriz de Canvas con
context.setTransform
, - Pero no puede leer la matriz de transformación interna de Canvas, es solo de escritura.
¿Por qué usar una matriz de transformación?
Una matriz de transformación le permite agregar muchas traducciones individuales, rotaciones y escalas en una matriz única y fácil de volver a aplicar.
Durante animaciones complejas, puede aplicar docenas (o cientos) de transformaciones a una forma. Al utilizar una matriz de transformación, puede (casi) volver a aplicar esas docenas de transformaciones con una sola línea de código.
Algunos ejemplos de uso:
Probar si el mouse está dentro de una forma que ha traducido, rotado y escalado
Existe un
context.isPointInPath
incorporado que comprueba si un punto (por ejemplo, el mouse) está dentro de una forma de trayectoria, pero esta prueba incorporada es muy lenta en comparación con las pruebas que usan una matriz.Comprobar de manera eficiente si el mouse está dentro de una forma implica tomar la posición del mouse informada por el navegador y transformarla de la misma manera que se transformó la forma. Luego, puede aplicar la prueba de impacto como si la forma no se hubiera transformado.
Redibuje una forma que haya sido traducida, girada y escalada extensivamente.
En lugar de volver a aplicar transformaciones individuales con múltiples
.translate, .rotate, .scale
, puede aplicar todas las transformaciones agregadas en una sola línea de código.Formas de prueba de colisión que han sido traducidas, rotadas y escaladas.
Puede usar geometría y trigonometría para calcular los puntos que conforman las formas transformadas, pero es más rápido usar una matriz de transformación para calcular esos puntos.
Una matriz de transformación "clase"
Este código refleja los comandos nativos de transformación context.translate
, context.rotate
, context.scale
. A diferencia de la matriz del lienzo nativo, esta matriz es legible y reutilizable.
Métodos:
translate
,rotate
,scale
los comandos de transformación de contexto y te permite alimentar transformaciones en la matriz. La matriz mantiene eficientemente las transformaciones agregadas.setContextTransform
toma un contexto y establece la matriz de ese contexto igual a esta matriz de transformación. Esto vuelve a aplicar de manera eficiente todas las transformaciones almacenadas en esta matriz al contexto.resetContextTransform
restablece la transformación del contexto a su estado predeterminado (== sin transformar).getTransformedPoint
toma un punto de coordenadas sin transformar y lo convierte en un punto transformado.getScreenPoint
toma un punto de coordenadas transformado y lo convierte en un punto sin transformar.getMatrix
devuelve las transformaciones agregadas en forma de matriz de matriz.
Código:
var TransformationMatrix=( function(){
// private
var self;
var m=[1,0,0,1,0,0];
var reset=function(){ var m=[1,0,0,1,0,0]; }
var multiply=function(mat){
var m0=m[0]*mat[0]+m[2]*mat[1];
var m1=m[1]*mat[0]+m[3]*mat[1];
var m2=m[0]*mat[2]+m[2]*mat[3];
var m3=m[1]*mat[2]+m[3]*mat[3];
var m4=m[0]*mat[4]+m[2]*mat[5]+m[4];
var m5=m[1]*mat[4]+m[3]*mat[5]+m[5];
m=[m0,m1,m2,m3,m4,m5];
}
var screenPoint=function(transformedX,transformedY){
// invert
var d =1/(m[0]*m[3]-m[1]*m[2]);
im=[ m[3]*d, -m[1]*d, -m[2]*d, m[0]*d, d*(m[2]*m[5]-m[3]*m[4]), d*(m[1]*m[4]-m[0]*m[5]) ];
// point
return({
x:transformedX*im[0]+transformedY*im[2]+im[4],
y:transformedX*im[1]+transformedY*im[3]+im[5]
});
}
var transformedPoint=function(screenX,screenY){
return({
x:screenX*m[0] + screenY*m[2] + m[4],
y:screenX*m[1] + screenY*m[3] + m[5]
});
}
// public
function TransformationMatrix(){
self=this;
}
// shared methods
TransformationMatrix.prototype.translate=function(x,y){
var mat=[ 1, 0, 0, 1, x, y ];
multiply(mat);
};
TransformationMatrix.prototype.rotate=function(rAngle){
var c = Math.cos(rAngle);
var s = Math.sin(rAngle);
var mat=[ c, s, -s, c, 0, 0 ];
multiply(mat);
};
TransformationMatrix.prototype.scale=function(x,y){
var mat=[ x, 0, 0, y, 0, 0 ];
multiply(mat);
};
TransformationMatrix.prototype.skew=function(radianX,radianY){
var mat=[ 1, Math.tan(radianY), Math.tan(radianX), 1, 0, 0 ];
multiply(mat);
};
TransformationMatrix.prototype.reset=function(){
reset();
}
TransformationMatrix.prototype.setContextTransform=function(ctx){
ctx.setTransform(m[0],m[1],m[2],m[3],m[4],m[5]);
}
TransformationMatrix.prototype.resetContextTransform=function(ctx){
ctx.setTransform(1,0,0,1,0,0);
}
TransformationMatrix.prototype.getTransformedPoint=function(screenX,screenY){
return(transformedPoint(screenX,screenY));
}
TransformationMatrix.prototype.getScreenPoint=function(transformedX,transformedY){
return(screenPoint(transformedX,transformedY));
}
TransformationMatrix.prototype.getMatrix=function(){
var clone=[m[0],m[1],m[2],m[3],m[4],m[5]];
return(clone);
}
// return public
return(TransformationMatrix);
})();
Manifestación:
Esta demostración utiliza la Matriz de transformación "Clase" anterior para:
Seguimiento (== guardar) la matriz de transformación de un rectángulo.
Redibuje el rectángulo transformado sin utilizar comandos de transformación de contexto.
Probar si el ratón ha hecho clic dentro del rectángulo transformado.
Código:
<!doctype html>
<html>
<head>
<style>
body{ background-color:white; }
#canvas{border:1px solid red; }
</style>
<script>
window.onload=(function(){
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
function reOffset(){
var BB=canvas.getBoundingClientRect();
offsetX=BB.left;
offsetY=BB.top;
}
var offsetX,offsetY;
reOffset();
window.onscroll=function(e){ reOffset(); }
window.onresize=function(e){ reOffset(); }
// Transformation Matrix "Class"
var TransformationMatrix=( function(){
// private
var self;
var m=[1,0,0,1,0,0];
var reset=function(){ var m=[1,0,0,1,0,0]; }
var multiply=function(mat){
var m0=m[0]*mat[0]+m[2]*mat[1];
var m1=m[1]*mat[0]+m[3]*mat[1];
var m2=m[0]*mat[2]+m[2]*mat[3];
var m3=m[1]*mat[2]+m[3]*mat[3];
var m4=m[0]*mat[4]+m[2]*mat[5]+m[4];
var m5=m[1]*mat[4]+m[3]*mat[5]+m[5];
m=[m0,m1,m2,m3,m4,m5];
}
var screenPoint=function(transformedX,transformedY){
// invert
var d =1/(m[0]*m[3]-m[1]*m[2]);
im=[ m[3]*d, -m[1]*d, -m[2]*d, m[0]*d, d*(m[2]*m[5]-m[3]*m[4]), d*(m[1]*m[4]-m[0]*m[5]) ];
// point
return({
x:transformedX*im[0]+transformedY*im[2]+im[4],
y:transformedX*im[1]+transformedY*im[3]+im[5]
});
}
var transformedPoint=function(screenX,screenY){
return({
x:screenX*m[0] + screenY*m[2] + m[4],
y:screenX*m[1] + screenY*m[3] + m[5]
});
}
// public
function TransformationMatrix(){
self=this;
}
// shared methods
TransformationMatrix.prototype.translate=function(x,y){
var mat=[ 1, 0, 0, 1, x, y ];
multiply(mat);
};
TransformationMatrix.prototype.rotate=function(rAngle){
var c = Math.cos(rAngle);
var s = Math.sin(rAngle);
var mat=[ c, s, -s, c, 0, 0 ];
multiply(mat);
};
TransformationMatrix.prototype.scale=function(x,y){
var mat=[ x, 0, 0, y, 0, 0 ];
multiply(mat);
};
TransformationMatrix.prototype.skew=function(radianX,radianY){
var mat=[ 1, Math.tan(radianY), Math.tan(radianX), 1, 0, 0 ];
multiply(mat);
};
TransformationMatrix.prototype.reset=function(){
reset();
}
TransformationMatrix.prototype.setContextTransform=function(ctx){
ctx.setTransform(m[0],m[1],m[2],m[3],m[4],m[5]);
}
TransformationMatrix.prototype.resetContextTransform=function(ctx){
ctx.setTransform(1,0,0,1,0,0);
}
TransformationMatrix.prototype.getTransformedPoint=function(screenX,screenY){
return(transformedPoint(screenX,screenY));
}
TransformationMatrix.prototype.getScreenPoint=function(transformedX,transformedY){
return(screenPoint(transformedX,transformedY));
}
TransformationMatrix.prototype.getMatrix=function(){
var clone=[m[0],m[1],m[2],m[3],m[4],m[5]];
return(clone);
}
// return public
return(TransformationMatrix);
})();
// DEMO starts here
// create a rect and add a transformation matrix
// to track it's translations, rotations & scalings
var rect={x:30,y:30,w:50,h:35,matrix:new TransformationMatrix()};
// draw the untransformed rect in black
ctx.strokeRect(rect.x, rect.y, rect.w, rect.h);
// Demo: label
ctx.font='11px arial';
ctx.fillText('Untransformed Rect',rect.x,rect.y-10);
// transform the canvas & draw the transformed rect in red
ctx.translate(100,0);
ctx.scale(2,2);
ctx.rotate(Math.PI/12);
// draw the transformed rect
ctx.strokeStyle='red';
ctx.strokeRect(rect.x, rect.y, rect.w, rect.h);
ctx.font='6px arial';
// Demo: label
ctx.fillText('Same Rect: Translated, rotated & scaled',rect.x,rect.y-6);
// reset the context to untransformed state
ctx.setTransform(1,0,0,1,0,0);
// record the transformations in the matrix
var m=rect.matrix;
m.translate(100,0);
m.scale(2,2);
m.rotate(Math.PI/12);
// use the rect's saved transformation matrix to reposition,
// resize & redraw the rect
ctx.strokeStyle='blue';
drawTransformedRect(rect);
// Demo: instructions
ctx.font='14px arial';
ctx.fillText('Demo: click inside the blue rect',30,200);
// redraw a rect based on it's saved transformation matrix
function drawTransformedRect(r){
// set the context transformation matrix using the rect's saved matrix
m.setContextTransform(ctx);
// draw the rect (no position or size changes needed!)
ctx.strokeRect( r.x, r.y, r.w, r.h );
// reset the context transformation to default (==untransformed);
m.resetContextTransform(ctx);
}
// is the point in the transformed rectangle?
function isPointInTransformedRect(r,transformedX,transformedY){
var p=r.matrix.getScreenPoint(transformedX,transformedY);
var x=p.x;
var y=p.y;
return(x>r.x && x<r.x+r.w && y>r.y && y<r.y+r.h);
}
// listen for mousedown events
canvas.onmousedown=handleMouseDown;
function handleMouseDown(e){
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
// get mouse position
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// is the mouse inside the transformed rect?
if(isPointInTransformedRect(rect,mouseX,mouseY)){
alert('You clicked in the transformed Rect');
}
}
// Demo: redraw transformed rect without using
// context transformation commands
function drawTransformedRect(r,color){
var m=r.matrix;
var tl=m.getTransformedPoint(r.x,r.y);
var tr=m.getTransformedPoint(r.x+r.w,r.y);
var br=m.getTransformedPoint(r.x+r.w,r.y+r.h);
var bl=m.getTransformedPoint(r.x,r.y+r.h);
ctx.beginPath();
ctx.moveTo(tl.x,tl.y);
ctx.lineTo(tr.x,tr.y);
ctx.lineTo(br.x,br.y);
ctx.lineTo(bl.x,bl.y);
ctx.closePath();
ctx.strokeStyle=color;
ctx.stroke();
}
}); // end window.onload
</script>
</head>
<body>
<canvas id="canvas" width=512 height=250></canvas>
</body>
</html>