Suche…
Viele übersetzte, skalierte und gedrehte Bilder schnell zeichnen
In vielen Situationen möchten Sie ein Bild zeichnen, das gedreht, skaliert und übersetzt wird. Die Drehung sollte um die Bildmitte herum erfolgen. Dies ist der schnellste Weg auf der 2D-Leinwand. Diese Funktionen eignen sich gut für 2D-Spiele, bei denen die Erwartung besteht, dass alle 60 Sekunden ein paar hundert Bilder und sogar mehr als 1000 Bilder gerendert werden. (abhängig von der 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
}
Eine Variante kann auch den Alpha-Wert enthalten, der für Partikelsysteme nützlich ist.
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 ist wichtig zu wissen, dass beide Funktionen den Leinwandkontext in einem zufälligen Zustand belassen. Obwohl die Funktionen nicht von anderen Rendering-Funktionen betroffen sein werden. Wenn Sie mit dem Rendern von Bildern fertig sind, müssen Sie möglicherweise die Standardumwandlung wiederherstellen
ctx.setTransform(1, 0, 0, 1, 0, 0); // set the context transform back to the default
Wenn Sie die Alpha-Version (zweites Beispiel) und dann die Standardversion verwenden, müssen Sie sicherstellen, dass der globale Alpha-Status wiederhergestellt wird
ctx.globalAlpha = 1;
Ein Beispiel für die Verwendung der obigen Funktionen zum Rendern einiger Partikel und einiger Bilder
// 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
Drehen Sie ein Bild oder einen Pfad um den Mittelpunkt
Die Schritte 1 bis 5 unten ermöglichen, dass ein Bild oder eine Pfadform an eine beliebige Stelle auf der Leinwand verschoben und in einen beliebigen Winkel gedreht werden kann, ohne die ursprünglichen Punktkoordinaten der Bild- / Pfadform zu ändern.
Verschieben Sie den Ursprung der Leinwand [0,0] zum Mittelpunkt der Form
context.translate( shapeCenterX, shapeCenterY );
Die Leinwand um den gewünschten Winkel drehen (im Bogenmaß)
context.rotate( radianAngle );
Bewegen Sie den Leinwandursprung zurück in die linke obere Ecke
context.translate( -shapeCenterX, -shapeCenterY );
Zeichnen Sie das Bild oder die Pfadform mit den ursprünglichen Koordinaten.
context.fillRect( shapeX, shapeY, shapeWidth, shapeHeight );
Immer aufräumen! Setzen Sie den Transformationsstatus wieder auf den Wert vor # 1
Schritt 5, Option 1: Machen Sie jede Transformation in umgekehrter Reihenfolge rück
// undo #3 context.translate( shapeCenterX, shapeCenterY ); // undo #2 context.rotate( -radianAngle ); // undo #1 context.translate( -shapeCenterX, shapeCenterY );
Schritt 5, Option 2: Wenn sich die Leinwand vor dem Beginn von Schritt 1 in einem nicht transformierten Zustand befunden hat (Standardeinstellung), können Sie die Auswirkungen der Schritte 1 bis 3 rückgängig machen, indem Sie alle Transformationen auf ihren Standardstatus zurücksetzen
// set transformation to the default state (==no transformation applied) context.setTransform(1,0,0,1,0,0)
Beispielcode Demo:
// 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 );
Einführung in Transformationen
Transformationen ändern die Startposition eines bestimmten Punkts, indem dieser Punkt verschoben, gedreht und skaliert wird.
- Übersetzung: Verschiebt einen Punkt um eine
distanceX
und einedistanceY
. - Rotation: Dreht einen Punkt um einen
radian angle
um seinen Rotationspunkt. Der Standarddrehpunkt in Html Canvas ist der obere linke Ursprung [x = 0, y = 0] des Canvas. Sie können den Drehpunkt jedoch mit Hilfe von Übersetzungen neu positionieren. - Skalierung:
scalingFactorX
die Position einesscalingFactorX
um einenscalingFactorX
und einenscalingFactorY
vom Skalierungspunkt aus. Der Standard-Skalierungspunkt in Html Canvas ist der linke obere Ursprung [x = 0, y = 0] des Canvas. Sie können den Skalierungspunkt jedoch mit Übersetzungen neu positionieren.
Sie können auch weniger gebräuchliche Transformationen durchführen, z. B. das Scheren (Verziehen), indem Sie die Transformationsmatrix der context.transform
direkt mit context.transform
.
Einen Punkt mit context.translate(75,25)
übersetzen (== verschieben context.translate(75,25)
Einen Punkt mit context.rotate(Math.PI/8)
context.scale(2,2)
einen Punkt mit context.scale(2,2)
Canvas erzielt tatsächlich Transformationen, indem das gesamte Koordinatensystem der Canvas geändert wird.
-
context.translate
verschiebt den Ursprung der Leinwand [0,0] von der oberen linken Ecke an eine neue Position. -
context.rotate
dreht das gesamte Leinwandkoordinatensystem um den Ursprung. -
context.scale
skaliert das gesamte Leinwandkoordinatensystem um den Ursprung.every x*=scaleX
dass die Größe jedes x, y auf der Leinwandevery x*=scaleX
:every x*=scaleX
undevery y*=scaleY
.
Canvas-Transformationen sind dauerhaft. Alle neuen Zeichnungen werden solange transformiert, bis Sie die Umwandlung der Zeichenfläche auf den Standardstatus zurücksetzen (== völlig untransformiert). Sie können den Standard auf folgende Weise zurücksetzen:
// reset context transformations to the default (untransformed) state
context.setTransform(1,0,0,1,0,0);
Eine Transformationsmatrix zum Verfolgen der übersetzten, gedrehten und skalierten Form (en)
Mit Canvas können Sie context.translate
, context.rotate
und context.scale
context.translate
, um Ihre Form in der context.scale
Position und Größe zu zeichnen.
Canvas selbst verwendet eine Transformationsmatrix, um Transformationen effizient zu verfolgen.
- Sie können die Matrix von Canvas mit
context.transform
- Sie können die Canvas-Matrix mit einzelnen Befehlen zum
translate, rotate & scale
Skalieren ändern - Sie können die Canvas-Matrix vollständig mit
context.setTransform
überschreiben, - Sie können die interne Transformationsmatrix von Canvas jedoch nicht lesen - sie ist nur schreibgeschützt.
Warum eine Transformationsmatrix verwenden?
Mit einer Transformationsmatrix können Sie viele einzelne Translationen, Rotationen und Skalierungen in einer einzigen, einfach erneut anzuwendenden Matrix zusammenfassen.
In komplexen Animationen können Sie Dutzende (oder Hunderte) von Transformationen auf eine Form anwenden. Durch die Verwendung einer Transformationsmatrix können Sie diese Dutzende von Transformationen (fast) sofort mit einer einzigen Codezeile erneut anwenden.
Einige Beispiele verwenden:
Testen Sie, ob sich die Maus in einer Form befindet, die Sie übersetzt, gedreht und skaliert haben
Es gibt einen eingebauten
context.isPointInPath
, der prüft, ob sich ein Punkt (z. B. die Maus) innerhalb einercontext.isPointInPath
befindet. Dieser integrierte Test ist jedoch im Vergleich zum Testen mit einer Matrix sehr langsam.Um effizient zu testen, ob sich die Maus innerhalb einer Form befindet, muss die vom Browser gemeldete Mausposition auf dieselbe Weise wie die Form transformiert werden. Dann können Sie Trefferprüfungen anwenden, als ob die Form nicht transformiert wurde.
Zeichnen Sie eine Form, die umfangreich übersetzt, gedreht und skaliert wurde.
Anstatt einzelne Transformationen mit mehreren
.translate, .rotate, .scale
Sie alle aggregierten Transformationen in einer einzigen Codezeile anwenden.Kollisionstestformen, die übersetzt, gedreht und skaliert wurden
Sie können Geometrie und Trigonometrie verwenden, um die Punkte zu berechnen, aus denen sich transformierte Formen zusammensetzen. Es ist jedoch schneller, diese Transformationsmatrix mit einer Transformationsmatrix zu berechnen.
Eine Transformationsmatrix "Klasse"
Dieser Code spiegelt die context.scale
Umwandlungsbefehle context.translate
, context.rotate
und context.scale
. Im Gegensatz zur nativen Canvas-Matrix ist diese Matrix lesbar und wiederverwendbar.
Methoden:
translate
,rotate
,scale
Spiegel die Kontexttransformationsbefehle und lassen Sie Transformationen in die Matrix ernähren. Die Matrix hält die aggregierten Transformationen effizient.setContextTransform
nimmt einen Kontext und setzt die Matrix dieses Kontexts dieser Transformationsmatrix gleich. Dadurch werden alle in dieser Matrix gespeicherten Transformationen wieder effizient auf den Kontext angewendet.resetContextTransform
setzt die Umwandlung des Kontexts in den Standardzustand zurück (== untransformiert).getTransformedPoint
nimmt einen nicht transformierten Koordinatenpunkt und konvertiert ihn in einen transformierten Punkt.getScreenPoint
nimmt einen transformierten Koordinatenpunkt und konvertiert ihn in einen nicht transformierten Punkt.getMatrix
gibt die aggregierten Transformationen in Form eines Matrix-Arrays zurück.
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);
})();
Demo:
Diese Demo verwendet die Transformationsmatrix "Klasse", um:
Verfolgung (= Speichern) der Transformationsmatrix eines Rechtecks.
Zeichnen Sie das transformierte Rechteck neu, ohne Kontextbefehle zu verwenden.
Testen Sie, ob die Maus in das transformierte Rechteck geklickt hat.
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>