web前端

canvas的线性变换原理

2022-09-05  本文已影响0人  姜治宇

线性变换

canvas提供的scale和rotate等都是很简单好用的api,但底层原理是什么呢?

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <canvas id="canvas" width="300" height="200" style="border:1px dashed gray;">
    </canvas>
    <button id="btn">移动</button>
</body>
</html>
<script>
var cnv = document.getElementById('canvas');
var cxt = cnv.getContext('2d');
cxt.fillStyle = 'blue';
cxt.fillRect(30,30,50,50);
document.getElementById('btn').onclick = function(){
    cxt.clearRect(0,0,cnv.width,cnv.height);//清除画布
    cxt.scale(1.5,1.5);//放大
    cxt.rotate(count * Math.PI / 10); //旋转
    cxt.fillStyle = 'blue';
    cxt.fillRect(30,30,50,50);//画出图形
}
</script>
缩放

先来看缩放。缩放的原理比较简单,假如缩放比例是s,缩放前的坐标是(x,y),缩放后的坐标就是(x',y'),我们用向量表示就是:


1.png
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <canvas id="canvas" width="500" height="500" style="border:1px dashed gray;">
    </canvas>
    <button id="btn">线性变换</button>
</body>

</html>
<script>
    var cnv = document.getElementById('canvas');
    var cxt = cnv.getContext('2d');
    cxt.fillStyle = 'blue';
    cxt.fillRect(30, 30, 50, 50);
    document.getElementById('btn').onclick = function () {
        cxt.clearRect(0, 0, cnv.width, cnv.height);//清除画布
        cxt.transform(1.5, 0, 0, 1.5, 0, 0);//放大 cxt.scale(1.5,1.5)
        cxt.fillStyle = 'blue';
        cxt.fillRect(30, 30, 50, 50);//画出图形

    }
</script>

可以看出,效果跟scale是一致的。
(1.5, 0, 0, 1.5, 0, 0)其实就是变换矩阵A,那多出来的俩0是干啥的呢?别急,后面会提到。

旋转

旋转稍微有点复杂。我们看一下图示:


2.png

我们还是假设旋转前的坐标是(x,y),旋转后的坐标就是(x',y'),旋转矩阵不知道,我们先假设一个,就是:


3.png
我们可以将变换前的一个点(1,0),与变换后的一个点(cosθ,sinθ)代入,就是:
3.png

然后将变换前的(0,1)点,与变换后的坐标(-sinθ,cosθ)代入,这个坐标可以用相似三角形得到,大家可以算一下。
就是:


4.png
这样变换矩阵我们就得出来了:
5.png
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <canvas id="canvas" width="500" height="500" style="border:1px dashed gray;">
    </canvas>
    <button id="btn">线性变换</button>
</body>

</html>
<script>
    var cnv = document.getElementById('canvas');
    var cxt = cnv.getContext('2d');
    var alpha = Math.PI / 10;//旋转弧度
    cxt.fillStyle = 'blue';
    cxt.fillRect(30, 30, 50, 50);
    document.getElementById('btn').onclick = function () {
        cxt.clearRect(0, 0, cnv.width, cnv.height);//清除画布
        cxt.transform(Math.cos(alpha), Math.sin(alpha), -Math.sin(alpha), Math.cos(alpha), 0, 0); //旋转
        cxt.fillStyle = 'blue';
        cxt.fillRect(30, 30, 50, 50);//画出图形

    }
</script>
平移

有了以上的基础,平移就好理解了。


6.png

也就是:
x'=x+tx
y'=y+ty
我们用向量表示:


7.png
对比前面的缩放和旋转,我们能否也写成b=Ax这样的方式呢?

齐次坐标

齐次坐标,就是将一个原本是n维的向量,用n+1维向量来表示。
要想将平移变换用b=Ax表达,我们可以再引入第n+1维辅助向量,即可解决问题。
不信请看:


8.png

联系前面的缩放和旋转,其实就是tx和ty为0的特殊情况。这样我们就可以把全部线性变换,用一个通用向量等式表达出来了:


9.png
去掉第n+1维辅助向量, 10.png
就是我们需要得到的变换矩阵A,也就是canvas的cxt.transform(A矩阵)。
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <canvas id="canvas" width="500" height="500" style="border:1px dashed gray;">
    </canvas>
    <button id="btn">平移</button>
</body>

</html>
<script>
    var cnv = document.getElementById('canvas');
    var cxt = cnv.getContext('2d');
    var alpha = Math.PI / 10;//旋转弧度

    cxt.fillStyle = 'blue';
    cxt.fillRect(30, 30, 50, 50);
    document.getElementById('btn').onclick = function () {
        cxt.clearRect(0, 0, cnv.width, cnv.height);//清除画布
        cxt.transform(1,0,0,1,20,20);//平移20px
       
        cxt.fillStyle = 'blue';
        cxt.fillRect(30, 30, 50, 50);//画出图形

    }
</script>
上一篇下一篇

猜你喜欢

热点阅读