Поиск…
Быстрое рисование многих переведенных, масштабированных и повернутых изображений
Есть много ситуаций, когда вы хотите нарисовать изображение, которое повернуто, масштабировано и переведено. Вращение должно происходить вокруг центра изображения. Это самый быстрый способ сделать это на 2D-холсте. Эти функции хорошо подходят для 2D-игр, где ожидание состоит в том, чтобы отображать несколько сотен даже до 1000+ изображений каждые 60 секунд. (в зависимости от аппаратного обеспечения)
// 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
}
Вариант также может включать альфа-значение, которое полезно для систем частиц.
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
}
Важно отметить, что обе функции оставляют контекст canvas в случайном состоянии. Хотя функции не будут затронуты другим образом. Когда вы закончите рендеринг изображений, вам может потребоваться восстановить преобразование по умолчанию
ctx.setTransform(1, 0, 0, 1, 0, 0); // set the context transform back to the default
Если вы используете альфа-версию (второй пример), а затем стандартную версию, вам нужно будет убедиться, что глобальное альфа-состояние восстановлено
ctx.globalAlpha = 1;
Пример использования вышеуказанных функций для рендеринга некоторых частиц и нескольких изображений
// 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
Поворот изображения или пути вокруг его центральной точки
Шаги 1-5 ниже позволяют произвольно перемещать любое изображение или форму пути в любом месте на холсте и поворачиваться на любой угол без изменения исходных координат точки изображения / пути.
Переместите холст [0,0] в центральную точку фигуры
context.translate( shapeCenterX, shapeCenterY );
Поверните холст на нужный угол (в радианах)
context.rotate( radianAngle );
Переместите начало холста обратно в верхний левый угол
context.translate( -shapeCenterX, -shapeCenterY );
Нарисуйте изображение или форму пути, используя его исходные координаты.
context.fillRect( shapeX, shapeY, shapeWidth, shapeHeight );
Всегда очищайте! Установите состояние преобразования обратно туда, где оно было до # 1
Шаг №5, Вариант №1: Отменить каждое преобразование в обратном порядке
// undo #3 context.translate( shapeCenterX, shapeCenterY ); // undo #2 context.rotate( -radianAngle ); // undo #1 context.translate( -shapeCenterX, shapeCenterY );
Шаг № 5, Вариант № 2: Если холст находился в нетрансформированном состоянии (по умолчанию) до начала шага № 1, вы можете отменить эффекты шагов № 1-3 путем сброса всех преобразований в их состояние по умолчанию
// set transformation to the default state (==no transformation applied) context.setTransform(1,0,0,1,0,0)
Пример демонстрации кода:
// 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 );
Введение в трансформации
Трансформации изменяют исходное положение данной точки, перемещая, поворачивая и масштабируя эту точку.
- Перевод: перемещает точку на
distanceX
иdistanceY
- Вращение: поворачивает точку под
radian angle
вокруг своей точки вращения. Точка вращения по умолчанию в холсте Html - это начало слева [x = 0, y = 0] холста. Но вы можете переместить точку поворота с помощью переводов. - Масштабирование: масштабирует положение точки с помощью
scalingFactorX
иscalingFactorY
от точки масштабирования. Точка масштабирования по умолчанию в холсте Html - это начало слева [x = 0, y = 0] холста. Но вы можете изменить положение масштабирования с помощью переводов.
Вы также можете выполнять менее распространенные преобразования, такие как сдвиг (перекос), путем непосредственного задания матрицы преобразования холста с помощью context.transform
.
context.translate(75,25)
(== move) точку с context.translate(75,25)
Поверните точку с помощью context.rotate(Math.PI/8)
Масштабировать точку с помощью context.scale(2,2)
Холст фактически достигает преобразований, изменяя всю систему координат холста.
-
context.translate
переместит начало холста [0,0] из верхнего левого угла в новое место. -
context.rotate
будет вращать всю систему координат холста вокруг начала координат. -
context.scale
будет масштабировать всю систему координат холста вокруг начала координат. Подумайте об этом как об увеличении размера каждого x, y на холсте:every x*=scaleX
иevery y*=scaleY
.
Преобразования холста являются постоянными. Все новые чертежи будут по-прежнему трансформироваться до тех пор, пока вы не вернете преобразование холста обратно в его состояние по умолчанию (== полностью нетрансформированное). Вы можете вернуться к умолчанию по умолчанию:
// reset context transformations to the default (untransformed) state
context.setTransform(1,0,0,1,0,0);
Матрица преобразования для отслеживания переведенной, повернутой и масштабированной формы (ов)
Canvas позволяет вам использовать context.translate
, context.rotate
и context.scale
, чтобы нарисовать вашу фигуру в нужном вам месте и размере.
Сам холст использует матрицу преобразования для эффективного отслеживания преобразований.
- Вы можете изменить матрицу Canvas с помощью
context.transform
- Вы можете изменить матрицу Canvas с помощью отдельных команд
translate, rotate & scale
- Вы можете полностью перезаписать матрицу Canvas с помощью
context.setTransform
, - Но вы не можете прочитать внутреннюю матрицу преобразования Canvas - она только для записи.
Зачем использовать матрицу преобразования?
Матрица преобразования позволяет объединять многие отдельные переводы, вращения и масштабирование в единую, легко заменяемую матрицу.
Во время сложных анимаций вы можете применить десятки (или сотни) преобразований к форме. Используя матрицу трансформации, вы можете (почти) мгновенно повторно применить эти десятки преобразований с помощью одной строки кода.
В некоторых примерах используется:
Проверьте, находится ли мышь внутри фигуры, которую вы перевели, повернули и масштабировали
Существует встроенный
context.isPointInPath
который проверяет, находится ли точка (например, мышь) внутри формы пути, но этот встроенный тест очень медленный по сравнению с тестированием с использованием матрицы.Эффективное тестирование, если мышь находится внутри формы, включает в себя определение местоположения мыши, сообщенное браузером, и преобразование его так же, как форма была преобразована. Затем вы можете применить хитовую проверку, как будто форма не была преобразована.
Перерисовать форму, которая была широко переведена, повернута и масштабирована.
Вместо повторного применения отдельных преобразований с несколькими
.translate, .rotate, .scale
вы можете применять все агрегированные преобразования в одной строке кода.Формы испытаний на столкновение, которые были переведены, повернуты и масштабированы
Вы можете использовать геометрию и тригонометрию для вычисления точек, которые составляют преобразованные фигуры, но быстрее использовать матрицу преобразования для вычисления этих точек.
Матрица преобразования «Класс»
Этот код отражает команды native context.translate
, context.rotate
, context.scale
. В отличие от встроенной матрицы холста, эта матрица читаема и многоразовая.
Методы:
translate
,rotate
,scale
зеркальные команды контекстного преобразования и позволять вам преобразовывать преобразования в матрицу. Матрица эффективно содержит агрегированные преобразования.setContextTransform
принимает контекст и устанавливает, что матрица контекста равна этой матрице преобразования. Это эффективно повторяет все преобразования, хранящиеся в этой матрице, в контексте.resetContextTransform
сбрасывает преобразование контекста в состояние по умолчанию (== untransformed).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);
})();
Демо - версия:
В этой демонстрации используется матрица трансформации «Класс» выше:
Track (== save) матрица преобразования прямоугольника.
Перерисовать преобразованный прямоугольник без использования команд преобразования контекста.
Проверьте, щелкнула ли мышь внутри преобразованного прямоугольника.
Код:
<!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>