Javascript HTML5画布转换与手动偏移?
关于画布性能,人们常说的一件事是,对上下文状态的更改(如平移、缩放、旋转等)代价高昂,应将其保持在最低限度(例如,通过将使用相同变换的绘图命令批处理在一起) 所以我的问题是,如果没有那么多从转换中受益的命令,并且不能真正批处理它们,那么在转换上使用手动偏移是否更好?还是做一个适当的变换总是更好 例如,如果我正在绘制一个小图形,每个图形可能由1-5个多边形组成,并且每个图形需要不同的变换(例如,不同的放置和旋转),当我可以用一点三角学计算正确的位置时,对每个图形进行完全变换似乎效率低下。用于变换(x,y定位)仅此而已,您也可以自己计算x,y,因为在绘制时必须提供该值 对于旋转、缩放等。对单个多边形使用单独的变换——必要时,变换并不昂贵。而且变换大多在速度更快的GPU上完成);-) 注意:使用Javascript HTML5画布转换与手动偏移?,javascript,performance,html5-canvas,Javascript,Performance,Html5 Canvas,关于画布性能,人们常说的一件事是,对上下文状态的更改(如平移、缩放、旋转等)代价高昂,应将其保持在最低限度(例如,通过将使用相同变换的绘图命令批处理在一起) 所以我的问题是,如果没有那么多从转换中受益的命令,并且不能真正批处理它们,那么在转换上使用手动偏移是否更好?还是做一个适当的变换总是更好 例如,如果我正在绘制一个小图形,每个图形可能由1-5个多边形组成,并且每个图形需要不同的变换(例如,不同的放置和旋转),当我可以用一点三角学计算正确的位置时,对每个图形进行完全变换似乎效率低下。用于变换(
context.setTransform(1,0,0,1,0,0,0)
重置单个转换,而不是context.save
,因为context.restore
将有保存/重置所有非转换上下文状态(样式等)的额外负担
有关如何使用变换矩阵跟踪单个变换的示例,请参见下文:
画布允许您
上下文。翻译,上下文。旋转和上下文。缩放,以便在所需的位置和大小绘制形状
Canvas本身使用转换矩阵来有效地跟踪转换
- 您可以使用
context.transform更改画布的矩阵
- 您可以使用单个
平移、旋转和缩放命令更改画布的矩阵
- 您可以使用
context.setTransform
完全覆盖画布的矩阵
- 但您无法读取Canvas的内部转换矩阵——它是只读的
为什么要使用转换矩阵?
变换矩阵允许您将许多单独的平移、旋转和缩放聚合为一个易于重新应用的矩阵
在复杂的动画过程中,您可能会对一个形状应用数十(或数百)个变换。通过使用转换矩阵,您可以(几乎)用一行代码立即重新应用这几十个转换
一些示例使用:
- 测试鼠标是否位于已平移、旋转和缩放的形状内
有一个内置的
上下文。isPointInPath
可以测试一个点(例如鼠标)是否在路径形状内,但是与使用矩阵进行测试相比,这个内置测试非常慢
有效地测试鼠标是否在形状内涉及到获取浏览器报告的鼠标位置,并以与形状变换相同的方式对其进行变换。然后,您可以应用命中测试,就好像形状没有变换一样
- 重新绘制经过广泛平移、旋转和缩放的形状。
您可以在一行代码中应用所有聚合的转换,而不是使用多个
.translate、.rotate、.scale
重新应用单个转换
- 已平移、旋转和缩放的碰撞测试形状
您可以使用几何和三角学来计算构成变换形状的点,但使用变换矩阵来计算这些点更快
一个变换矩阵“类”
此代码镜像本机上下文。translate
,上下文。rotate
,上下文。scale
转换命令。与本机画布矩阵不同,此矩阵可读且可重用
方法:
翻译
,旋转
,缩放
镜像上下文转换命令,并允许您将转换馈送到矩阵中。矩阵有效地保存聚合的变换
setContextTransform
获取一个上下文,并将该上下文的矩阵设置为该转换矩阵。这将有效地将存储在此矩阵中的所有转换重新应用于上下文
resetContextTransform
将上下文的转换重置为默认状态(=未转换)
getTransformedPoint
获取未转换的坐标点并将其转换为转换点
getScreenPoint
获取转换后的坐标点并将其转换为未转换的点
getMatrix
以矩阵数组的形式返回聚合的转换
代码:
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);
})();
演示:
此演示使用上面的转换矩阵“类”来:
- 跟踪(=保存)矩形的变换矩阵
- 在不使用上下文转换命令的情况下重新绘制转换后的矩形
- 测试鼠标是否在变换的矩形内单击
代码:
正文{背景色:白色;}
#画布{边框:1px纯红;}
window.onload=(函数(){
var canvas=document.getElementById(“canvas”);
var ctx=canvas.getContext(“2d”);
var cw=画布宽度;
var ch=画布高度;
函数reOffset(){
var BB=canvas.getBoundingClientRect();
offsetX=BB.left;
offsetY=BB.top;
}
var offsetX,offsetY;
reOffset();
onscroll=函数(e){reOffset();}
onresize=函数(e){reOffset();}
//Transformati
<!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>