Recherche…
Dessiner rapidement de nombreuses images traduites, mises à l'échelle et pivotées
Il existe de nombreuses situations où vous souhaitez dessiner une image qui est pivotée, mise à l'échelle et traduite. La rotation devrait avoir lieu autour du centre de l'image. C'est le moyen le plus rapide de le faire sur la toile 2D. Ces fonctions sont bien adaptées aux jeux en 2D où l'on s'attend à rendre quelques centaines voire plus de 1000 images toutes les 60 secondes. (dépend du matériel)
// 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
}
Une variante peut également inclure la valeur alpha qui est utile pour les systèmes de particules.
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
}
Il est important de noter que les deux fonctions laissent le contexte du canevas dans un état aléatoire. Bien que les fonctions ne seront pas affectées les autres rendant mon être. Lorsque vous avez fini de rendre les images, vous devrez peut-être restaurer la transformation par défaut
ctx.setTransform(1, 0, 0, 1, 0, 0); // set the context transform back to the default
Si vous utilisez la version alpha (deuxième exemple) et la version standard, vous devrez vous assurer que l'état alpha global est restauré
ctx.globalAlpha = 1;
Un exemple d'utilisation des fonctions ci-dessus pour rendre certaines particules et quelques images
// 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
Faire pivoter une image ou un chemin autour de son centre
Les étapes 1 à 5 ci-dessous permettent de déplacer n'importe quelle image ou forme de chemin n'importe où sur la toile et de la faire pivoter à n'importe quel angle sans modifier les coordonnées du point d'origine de l'image / du chemin.
Déplacer l'origine du canevas [0,0] vers le centre de la forme
context.translate( shapeCenterX, shapeCenterY );
Faire pivoter la toile de l'angle souhaité (en radians)
context.rotate( radianAngle );
Déplacer l'origine du canevas vers le coin supérieur gauche
context.translate( -shapeCenterX, -shapeCenterY );
Dessinez l'image ou la forme du chemin en utilisant ses coordonnées d'origine.
context.fillRect( shapeX, shapeY, shapeWidth, shapeHeight );
Toujours nettoyer! Rétablit l'état de transformation à l'endroit où il était avant # 1
Étape n ° 5, option n ° 1: Annulez chaque transformation dans l'ordre inverse
// undo #3 context.translate( shapeCenterX, shapeCenterY ); // undo #2 context.rotate( -radianAngle ); // undo #1 context.translate( -shapeCenterX, shapeCenterY );
Étape 5, Option 2: Si le canevas était dans un état non transformé (valeur par défaut) avant de commencer l'étape 1, vous pouvez annuler les effets des étapes 1 à 3 en réinitialisant toutes les transformations à leur état par défaut.
// set transformation to the default state (==no transformation applied) context.setTransform(1,0,0,1,0,0)
Exemple de démonstration de code:
// 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 );
Introduction aux transformations
Les transformations modifient la position de départ d'un point donné en déplaçant, en tournant et en mettant à l'échelle ce point.
- Traduction: Déplace un point de
distanceX
et dedistanceY
. - Rotation: fait pivoter un point par un
radian angle
autour de son point de rotation. Le point de rotation par défaut dans Canevas HTML est l'origine supérieure gauche [x = 0, y = 0] du canevas. Mais vous pouvez repositionner le point de rotation en utilisant des traductions. - Mise à l'échelle:
scalingFactorY
à l'échelle la position d'un point par unscalingFactorX
et unscalingFactorY
depuis son point de mise à l'échelle. Le point de mise à l'échelle par défaut dans Canvas HTML est l'origine supérieure gauche [x = 0, y = 0] du canevas. Mais vous pouvez repositionner le point de mise à l'échelle à l'aide de traductions.
Vous pouvez également effectuer des transformations moins courantes, telles que le cisaillement (skewing), en définissant directement la matrice de transformation du canevas à l'aide de context.transform
.
Traduire (== déplacer) un point avec context.translate(75,25)
Faire pivoter un point avec context.rotate(Math.PI/8)
Mettre à l'échelle un point avec context.scale(2,2)
Canvas réalise en réalité des transformations en modifiant le système de coordonnées complet de la toile.
-
context.translate
déplace l'origine du canevas [0,0] du coin supérieur gauche vers un nouvel emplacement. -
context.rotate
fera pivoter tout le système de coordonnées du canevas autour de l'origine. -
context.scale
mettra à l'échelle le système de coordonnées du canevas entier autour de l'origine. Considérez ceci comme augmentant la taille de chaque x, y sur le canevas:every x*=scaleX
etevery y*=scaleY
.
Les transformations de la toile sont persistantes. Tous les nouveaux dessins continueront à être transformés jusqu'à ce que vous réinitialisiez la transformation du canevas à son état par défaut (== totalement non transformé). Vous pouvez revenir à la valeur par défaut avec:
// reset context transformations to the default (untransformed) state
context.setTransform(1,0,0,1,0,0);
Une matrice de transformation pour suivre les formes traduites, pivotées et mises à l'échelle
Canvas vous permet de context.translate
, context.rotate
et context.scale
afin de dessiner votre forme dans la position et la taille souhaitées.
Canvas lui-même utilise une matrice de transformation pour suivre efficacement les transformations.
- Vous pouvez changer la matrice de Canvas avec
context.transform
- Vous pouvez changer la matrice de Canvas avec des commandes individuelles de
translate, rotate & scale
- Vous pouvez complètement écraser la matrice de Canvas avec
context.setTransform
, - Mais vous ne pouvez pas lire la matrice de transformation interne de Canvas - elle est en écriture seule.
Pourquoi utiliser une matrice de transformation?
Une matrice de transformation vous permet d'agréger de nombreuses traductions, rotations et échelles individuelles en une seule matrice facilement réappliquée.
Lors d'animations complexes, vous pouvez appliquer des dizaines (ou des centaines) de transformations à une forme. En utilisant une matrice de transformation, vous pouvez (presque) réappliquer instantanément ces dizaines de transformations avec une seule ligne de code.
Quelques exemples d'utilisation:
Tester si la souris est dans une forme que vous avez traduite, tournée et mise à l'échelle
Il y a un
context.isPointInPath
intégré qui teste si un point (par exemple la souris) se trouve dans une forme de chemin, mais ce test intégré est très lent comparé au test utilisant une matrice.Tester efficacement si la souris est dans une forme implique de prendre la position de la souris signalée par le navigateur et de la transformer de la même manière que la forme a été transformée. Vous pouvez ensuite appliquer le test de frappe comme si la forme n'était pas transformée.
Redessinez une forme qui a été largement traduite, pivotée et mise à l'échelle.
Au lieu de réappliquer des transformations individuelles avec plusieurs
.translate, .rotate, .scale
vous pouvez appliquer toutes les transformations agrégées sur une seule ligne de code.Formes de test de collision traduites, pivotées et mises à l'échelle
Vous pouvez utiliser la géométrie et la trigonométrie pour calculer les points constituant les formes transformées, mais il est plus rapide d'utiliser une matrice de transformation pour calculer ces points.
Une matrice de transformation "classe"
Ce code reflète les commandes de transformation context.translate
, context.rotate
et context.scale
natives. Contrairement à la matrice de canevas native, cette matrice est lisible et réutilisable.
Méthodes:
translate
,rotate
,scale
les commandes de transformation du contexte et vous permettre d'alimenter les transformations dans la matrice. La matrice contient efficacement les transformations agrégées.setContextTransform
prend un contexte et définit la matrice de ce contexte sur cette matrice de transformation. Cela réapplique efficacement toutes les transformations stockées dans cette matrice au contexte.resetContextTransform
réinitialise la transformation du contexte à son état par défaut (== non transformé).getTransformedPoint
prend un point de coordonnées non transformé et le convertit en un point transformé.getScreenPoint
prend un point de coordonnées transformé et le convertit en un point non transformé.getMatrix
renvoie les transformations agrégées sous la forme d'un tableau matriciel.
Code:
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);
})();
Démo:
Cette démo utilise la matrice de transformation "Class" ci-dessus pour:
Suivre (== enregistrer) la matrice de transformation d'un rectangle.
Redessinez le rectangle transformé sans utiliser les commandes de transformation de contexte.
Testez si la souris a cliqué à l'intérieur du rectangle transformé.
Code:
<!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>