Ricerca…
Disegnare rapidamente molte immagini tradotte, ridimensionate e ruotate
Ci sono molte situazioni in cui si desidera disegnare un'immagine che viene ruotata, ridimensionata e tradotta. La rotazione dovrebbe avvenire attorno al centro dell'immagine. Questo è il modo più rapido per farlo sulla tela 2D. Queste funzioni sono molto adatte ai giochi 2D in cui l'aspettativa è di rendere alcune centinaia persino fino a 1000+ immagini ogni 60esimo di secondo. (dipende dall'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 può anche includere il valore alfa che è utile per i sistemi di particelle.
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
}
È importante notare che entrambe le funzioni lasciano il contesto della tela in uno stato casuale. Anche se le funzioni non saranno influenzate, altri renderanno il mio essere. Una volta terminato il rendering delle immagini, potrebbe essere necessario ripristinare la trasformazione predefinita
ctx.setTransform(1, 0, 0, 1, 0, 0); // set the context transform back to the default
Se si utilizza la versione alpha (secondo esempio) e quindi la versione standard, è necessario assicurarsi che lo stato alfa globale venga ripristinato
ctx.globalAlpha = 1;
Un esempio di utilizzo delle funzioni di cui sopra per rendere alcune particelle e alcune immagini
// 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
Ruota un'immagine o un percorso attorno al suo punto centrale
I passaggi da 1 a 5 qui sotto consentono a qualsiasi immagine o forma del percorso di essere spostati ovunque nell'area di disegno e ruotati su qualsiasi angolo senza modificare le coordinate del punto originale dell'immagine / percorso.
Sposta l'origine della tela [0,0] al punto centrale della forma
context.translate( shapeCenterX, shapeCenterY );
Ruota la tela dell'angolo desiderato (in radianti)
context.rotate( radianAngle );
Sposta l'origine della tela nell'angolo in alto a sinistra
context.translate( -shapeCenterX, -shapeCenterY );
Disegna l'immagine o la forma del percorso usando le sue coordinate originali.
context.fillRect( shapeX, shapeY, shapeWidth, shapeHeight );
Pulisci sempre! Impostare lo stato di trasformazione su dove era prima del # 1
Passaggio n. 5, opzione n. 1: annullare ogni trasformazione nell'ordine inverso
// undo #3 context.translate( shapeCenterX, shapeCenterY ); // undo #2 context.rotate( -radianAngle ); // undo #1 context.translate( -shapeCenterX, shapeCenterY );
Passaggio n. 5, opzione n. 2: se la tela era in uno stato non trasformato (impostazione predefinita) prima di iniziare il passaggio n. 1, è possibile annullare gli effetti dei passaggi n. 1-3 ripristinando tutte le trasformazioni al loro stato predefinito
// set transformation to the default state (==no transformation applied) context.setTransform(1,0,0,1,0,0)
Esempio di demo del codice:
// 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 );
Introduzione alle trasformazioni
Le trasformazioni alterano la posizione di partenza di un dato punto spostando, ruotando e ridimensionando quel punto.
- Traslazione: sposta un punto di una
distanceX
e unadistanceX
distanceY
- Rotazione: ruota un punto di un
radian angle
attorno al suo punto di rotazione. Il punto di rotazione predefinito in Html Canvas è l'origine in alto a sinistra [x = 0, y = 0] della tela. Ma puoi riposizionare il punto di rotazione usando le traduzioni. - Ridimensionamento: consente di ridimensionare la posizione di un punto mediante un
scalingFactorX
di scala e unscalingFactorY
di scala dal punto di ridimensionamento. Il punto di ridimensionamento predefinito in Html Canvas è l'origine in alto a sinistra [x = 0, y = 0] della tela. Ma puoi riposizionare il punto di ridimensionamento usando le traduzioni.
È anche possibile eseguire trasformazioni meno comuni, come la cesoiatura (inclinazione), impostando direttamente la matrice di trasformazione della tela utilizzando context.transform
.
Traduci (== sposta) un punto con context.translate(75,25)
Ruota un punto con context.rotate(Math.PI/8)
Ridimensiona un punto con context.scale(2,2)
Canvas realizza effettivamente le trasformazioni alterando l'intero sistema di coordinate della tela.
-
context.translate
sposta l'origine del canvas [0,0] dall'angolo in alto a sinistra in una nuova posizione. -
context.rotate
ruoterà l'intero sistema di coordinate del canvas attorno all'origine. -
context.scale
ridimensiona l'intero sistema di coordinate del canvas attorno all'origine. Pensa a ciò aumentando la dimensione di ogni x, y sulla tela:every x*=scaleX
eevery y*=scaleY
.
Le trasformazioni della tela sono persistenti. Tutti i nuovi disegni continueranno a essere trasformati fino a quando non si ripristinerà la trasformazione della tela al suo stato predefinito (== totalmente non trasformato). Puoi ripristinare i valori predefiniti con:
// reset context transformations to the default (untransformed) state
context.setTransform(1,0,0,1,0,0);
Una matrice di trasformazione per tracciare le forme tradotte, ruotate e ridimensionate
Canvas ti permette di context.translate
, context.rotate
e context.scale
per disegnare la tua forma nella posizione e dimensione richiesta.
La tela stessa usa una matrice di trasformazione per tracciare efficientemente le trasformazioni.
- Puoi cambiare la matrice di Canvas con
context.transform
- Puoi cambiare la matrice di Canvas con singoli comandi di
translate, rotate & scale
scalatura - Puoi sovrascrivere completamente la matrice di Canvas con
context.setTransform
, - Ma non puoi leggere la matrice di trasformazione interna di Canvas: è di sola scrittura.
Perché usare una matrice di trasformazione?
Una matrice di trasformazione consente di aggregare molte singole traduzioni, rotazioni e ridimensionamenti in un'unica matrice facilmente riapplicabile.
Durante animazioni complesse è possibile applicare dozzine (o centinaia) di trasformazioni a una forma. Utilizzando una matrice di trasformazione è possibile (quasi) riapplicare istantaneamente quelle dozzine di trasformazioni con una singola riga di codice.
Alcuni esempi utilizza:
Verifica se il mouse si trova all'interno di una forma che hai tradotto, ruotato e ridimensionato
Esiste un
context.isPointInPath
integrato che verifica se un punto (ad es. Il mouse) si trova all'interno di un percorso, ma questo test integrato è molto lento rispetto al test che utilizza una matrice.Testare in modo efficiente se il mouse si trova all'interno di una forma comporta prendere la posizione del mouse riportata dal browser e trasformarla nello stesso modo in cui la forma è stata trasformata. Quindi puoi applicare hit-test come se la forma non fosse stata trasformata.
Ridisegna una forma che è stata ampiamente tradotta, ruotata e ridimensionata.
Invece di riapplicare le singole trasformazioni con più
.translate, .rotate, .scale
è possibile applicare tutte le trasformazioni aggregate in una singola riga di codice.Forme di prova di collisione che sono state tradotte, ruotate e ridimensionate
È possibile utilizzare la geometria e la trigonometria per calcolare i punti che costituiscono le forme trasformate, ma è più rapido utilizzare una matrice di trasformazione per calcolare quei punti.
Una matrice di trasformazione "Classe"
Questo codice rispecchia i comandi di trasformazione nativi context.translate
, context.rotate
, context.scale
. A differenza della matrice tela nativa, questa matrice è leggibile e riutilizzabile.
metodi:
translate
,rotate
,scale
i comandi di trasformazione del contesto e consenti di alimentare le trasformazioni nella matrice. La matrice contiene efficientemente le trasformazioni aggregate.setContextTransform
prende un contesto e imposta la matrice del contesto uguale a questa matrice di trasformazione. Questo riapplica in modo efficiente tutte le trasformazioni memorizzate in questa matrice al contesto.resetContextTransform
reimposta la trasformazione del contesto nel suo stato predefinito (== non trasformato).getTransformedPoint
prende un punto di coordinate non trasformato e lo converte in un punto trasformato.getScreenPoint
prende un punto di coordinate trasformato e lo converte in un punto non trasformato.getMatrix
restituisce le trasformazioni aggregate sotto forma di matrice matrice.
Codice:
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:
Questa demo utilizza la "Classe" di Transformation Matrix sopra per:
Traccia (== salva) la matrice di trasformazione di un rettangolo.
Ridisegna il rettangolo trasformato senza utilizzare i comandi di trasformazione del contesto.
Verifica se il mouse ha fatto clic all'interno del rettangolo trasformato.
Codice:
<!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>