Zoeken…
Veel vertaalde, geschaalde en geroteerde afbeeldingen snel tekenen
Er zijn veel situaties waarin u een afbeelding wilt tekenen die is gedraaid, geschaald en vertaald. De rotatie moet plaatsvinden rond het midden van de afbeelding. Dit is de snelste manier om dit op het 2D-canvas te doen. Deze functies zijn zeer geschikt voor 2D-games waarbij de verwachting is om een paar honderd tot maximaal 1000 afbeeldingen per 60 seconden te maken. (afhankelijk van de 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
}
Een variant kan ook de alfawaarde bevatten die handig is voor deeltjessystemen.
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
}
Het is belangrijk op te merken dat beide functies de canvascontext in een willekeurige staat verlaten. Hoewel de functies niet worden beïnvloed, wordt mijn weergave anders. Wanneer u klaar bent met het renderen van afbeeldingen, moet u mogelijk de standaardtransformatie herstellen
ctx.setTransform(1, 0, 0, 1, 0, 0); // set the context transform back to the default
Als u de alfaversie (tweede voorbeeld) en vervolgens de standaardversie gebruikt, moet u ervoor zorgen dat de globale alfastatus wordt hersteld
ctx.globalAlpha = 1;
Een voorbeeld van het gebruik van de bovenstaande functies om enkele deeltjes en enkele afbeeldingen weer te geven
// 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
Roteer een afbeelding of pad rond het middelpunt
Met stappen # 1-5 hieronder kan elke afbeelding of padvorm zowel naar elke plek op het canvas worden verplaatst als in elke hoek worden gedraaid zonder de oorspronkelijke puntcoördinaten van de afbeelding / padvorm te wijzigen.
Verplaats de canvas [0,0] oorsprong naar het middelpunt van de vorm
context.translate( shapeCenterX, shapeCenterY );
Roteer het canvas met de gewenste hoek (in radialen)
context.rotate( radianAngle );
Verplaats de canvasoorsprong terug naar de linkerbovenhoek
context.translate( -shapeCenterX, -shapeCenterY );
Teken de afbeelding of padvorm met behulp van de oorspronkelijke coördinaten.
context.fillRect( shapeX, shapeY, shapeWidth, shapeHeight );
Altijd opruimen! Zet de transformatiestatus terug naar waar deze vóór # 1 was
Stap # 5, Optie # 1: Maak elke transformatie ongedaan in de omgekeerde volgorde
// undo #3 context.translate( shapeCenterX, shapeCenterY ); // undo #2 context.rotate( -radianAngle ); // undo #1 context.translate( -shapeCenterX, shapeCenterY );
Stap # 5, Optie # 2: Als het canvas in een niet-getransformeerde staat was (de standaardinstelling) voordat u aan stap # 1 begon, kunt u de effecten van stappen # 1-3 ongedaan maken door alle transformaties terug te zetten naar hun standaardstatus
// set transformation to the default state (==no transformation applied) context.setTransform(1,0,0,1,0,0)
Voorbeeld van codedemo:
// 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 );
Inleiding tot transformaties
Transformaties veranderen de startpositie van een bepaald punt door dat punt te verplaatsen, roteren en schalen.
- Vertaling: Verplaatst een punt met een
distanceX
endistanceY
. - Rotatie: roteert een punt met een
radian angle
rond het rotatiepunt. Het standaard rotatiepunt in HTML Canvas is de oorsprong linksboven [x = 0, y = 0] van het Canvas. Maar u kunt het rotatiepunt verplaatsen met behulp van vertalingen. - Schalen: schaalt de positie van een punt door een
scalingFactorX
enscalingFactorY
vanaf hetscalingFactorY
. Het standaard schaalpunt in Html Canvas is de oorsprong linksboven [x = 0, y = 0] van het Canvas. Maar u kunt het schaalpunt verplaatsen met behulp van vertalingen.
U kunt ook minder algemene transformaties uitvoeren, zoals knippen (scheeftrekken), door de transformatiematrix van het canvas rechtstreeks in te stellen met context.transform
.
Een punt vertalen (== verplaatsen) met context.translate(75,25)
Een punt roteren met context.rotate(Math.PI/8)
Een punt schalen met context.scale(2,2)
Canvas realiseert eigenlijk transformaties door het hele coördinatensysteem van het canvas te wijzigen.
-
context.translate
verplaatst de oorsprong van het canvas [0,0] vanuit de linkerbovenhoek naar een nieuwe locatie. -
context.rotate
roteert het hele canvascoördinatensysteem rond de oorsprong. -
context.scale
schaalt het hele canvascoördinatensysteem rond de oorsprong. Zie dit als het vergroten van de grootte van elke x, y op het canvas:every x*=scaleX
enevery y*=scaleY
.
Canvastransformaties zijn persistent. Alle nieuwe tekeningen worden getransformeerd totdat u de transformatie van het canvas terugzet naar de standaardstatus (== volledig niet-getransformeerd). U kunt de standaardwaarden herstellen met:
// reset context transformations to the default (untransformed) state
context.setTransform(1,0,0,1,0,0);
Een transformatiematrix om vertaalde, geroteerde en geschaalde vorm (en) bij te houden
Met Canvas kunt u context.translate
, context.rotate
en context.scale
om uw vorm te tekenen in de gewenste positie en grootte.
Canvas zelf gebruikt een transformatiematrix om transformaties efficiënt te volgen.
- U kunt de matrix van Canvas wijzigen met
context.transform
- U kunt de matrix van Canvas wijzigen met afzonderlijke opdrachten voor
translate, rotate & scale
- Je kunt Canvas's matrix volledig overschrijven met
context.setTransform
, - Maar je kunt de interne transformatiematrix van Canvas niet lezen - het is alleen-schrijven.
Waarom een transformatiematrix gebruiken?
Met een transformatiematrix kunt u veel individuele vertalingen, rotaties en schalen samenvoegen tot een enkele, eenvoudig opnieuw toe te passen matrix.
Tijdens complexe animaties kunt u tientallen (of honderden) transformaties op een vorm toepassen. Door een transformatiematrix te gebruiken, kunt u die tientallen transformaties (bijna) onmiddellijk opnieuw toepassen met een enkele coderegel.
Sommige voorbeelden gebruiken:
Test of de muis zich in een vorm bevindt die u hebt vertaald, gedraaid en geschaald
Er is een ingebouwde
context.isPointInPath
die test of een punt (bijvoorbeeld de muis) zich in een padvorm bevindt, maar deze ingebouwde test is erg langzaam in vergelijking met testen met een matrix.Als u de muis efficiënt in een vorm wilt testen, moet u de door de browser gerapporteerde muispositie innemen en deze op dezelfde manier transformeren als de vorm. Vervolgens kunt u hit-testen toepassen alsof de vorm niet is getransformeerd.
Teken een vorm opnieuw die uitgebreid is vertaald, gedraaid en geschaald.
In plaats van afzonderlijke transformaties opnieuw toe te passen met meerdere
.translate, .rotate, .scale
kunt u alle geaggregeerde transformaties in een enkele.translate, .rotate, .scale
toepassen.Botsingstestvormen die zijn vertaald, gedraaid en geschaald
U kunt geometrie en trigonometrie gebruiken om de punten te berekenen waaruit getransformeerde vormen bestaan, maar het is sneller om een transformatiematrix te gebruiken om die punten te berekenen.
Een transformatiematrix "Klasse"
Deze code weerspiegelt de native transformatieopdrachten context.translate
, context.rotate
, context.scale
. In tegenstelling tot de native canvas-matrix is deze matrix leesbaar en herbruikbaar.
methoden:
translate
,rotate
,scale
spiegelt de contexttransformatieopdrachten en stelt u in staat transformaties in de matrix in te voeren. De matrix bevat efficiënt de geaggregeerde transformaties.setContextTransform
neemt een context en stelt de matrix van die context gelijk aan deze transformatiematrix. Hierdoor worden alle transformaties die in deze matrix zijn opgeslagen, opnieuw toegepast op de context.resetContextTransform
zet de transformatie van de context terug naar de standaardstatus (== niet-getransformeerd).getTransformedPoint
neemt een niet-getransformeerd coördinaatpunt en converteert het naar een getransformeerd punt.getScreenPoint
neemt een getransformeerd coördinaatpunt en converteert het naar een niet-getransformeerd punt.getMatrix
retourneert de geaggregeerde transformaties in de vorm van een matrixmatrix.
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:
Deze demo gebruikt de Transformation Matrix "Class" hierboven om:
Houd de transformatiematrix van een rechthoek bij (== opslaan).
Teken de getransformeerde rechthoek opnieuw zonder opdrachten voor contexttransformatie te gebruiken.
Test of de muis in de getransformeerde rechthoek heeft geklikt.
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>