《HTML5 Canvas游戏开发实战》——3.1 变形

简介: 本节书摘来自华章计算机《HTML5 Canvas游戏开发实战》一书中的第3章,第3.1节,作者:张路斌著, 更多章节内容可以访问云栖社区“华章计算机”公众号查看。

3.1 变形

默认情况下,一个画布的坐标空间会使用画布的左上角 (0,0) 作为原点,x 值向右增加,y 值向下增加。这个坐标空间中的单位通常会被转换为像素,然后,可通过转换坐标空间在绘图过程中实现移动、缩放或旋转等操作。这些操作是通过 translate()、scale() 和 rotate()等方法来实现的,它们会对画布的变换矩阵产生影响。
3.1.1 放大与缩小
我们用scale函数来实现图形的放大和缩小。
其函数原型如下:

scale(x, y);

其中,第一个参数x表示在x轴进行缩放,即水平缩放,第二个参数 y表示在y轴上进行缩放,即在竖直方向上进行缩放。
具体实现方法如代码清单3-1所示。
代码清单 3-1

<script type="text/javascript">
var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");
ctx.beginPath();
ctx.strokeStyle = "#000000";
ctx.strokeRect(10,10,150,100);

ctx.scale(3,3);
ctx.beginPath();
ctx.strokeStyle = "#cccccc";
ctx.strokeRect(10,10,150,100);
</script>

运行后的效果如图3-1所示。
下面解释一下代码清单3-1的代码。首先,以页面上的坐标(10,10)为起点开始画了一个宽150、高100的黑色矩形,如下所示:

ctx.beginPath();
ctx.strokeStyle = "#000000";
ctx.strokeRect(10,10,150,100);

image

然后,沿着x轴和y轴方向将其值放大3倍,并用同样的代码重新画一个灰色的矩形,如下所示:

ctx.scale(3,3);
ctx.beginPath();
ctx.strokeStyle = "#cccccc";
ctx.strokeRect(10,10,150,100);

从运行效果图可以看到,虽然用了同样的代码,但是第二次画的灰色矩形被放大了3倍,并且起始坐标值也一起被放大了3倍。
上述代码清单3-1实现了放大功能,那么现在来看看缩小功能如何实现,如代码清单3-2所示。
代码清单 3-2

<script type="text/javascript">
var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");
ctx.beginPath();
ctx.strokeStyle = "#000000";
ctx.strokeRect(50,50,150,100);

ctx.scale(0.5,0.5);
ctx.beginPath();
ctx.strokeStyle = "#cccccc";
ctx.strokeRect(50,50,150,100);
</script>

运行后的效果如图3-2所示。

image

在代码清单3-2中,同样先画出了一个黑色矩形,然后沿着x轴和y轴方向上分别将其值缩小到了0.5倍,并用同样的代码重新画了一个灰色的矩形。
从运行效果图可以看到,虽然用了同样的代码,但是第二次画的灰色矩形被缩小为原来的0.5倍了,并且起始坐标值也一起缩小了0.5倍。
另外还需要了解的是,使用scale函数时,如果将参数设置为负数,还可以使图形翻转,比如代码清单3-3就实现了竖直方向的翻转。
代码清单 3-3

<!DOCTYPE HTML>
<html>
<head>
  <meta charset="utf-8" />
</head>
<body>
<canvas id="myCanvas" width="500" height="350" style="background-color: #cccccc;">
你的浏览器不支持HTML5
</canvas>
<script type="text/javascript">
var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");

var image = new Image();    
image.src = "face.jpg";  
image.onload = function(){    
    ctx.drawImage(image,10,10);  
    ctx.scale(1,-1);
    ctx.drawImage(image,250,-250);
};  
</script>
</body>
</html>

运行效果如图3-3所示。

image

3.1.2 平移
使用translate函数可以实现对图形进行平移的功能。
其函数原型如下:

translate (x, y);

其中,第一个参数x表示在x轴进行平移,即在水平方向上平移,第二个参数 y表示在y轴上进行缩放,即在竖直方向上平移。
具体的实现方法如代码清单3-4所示。
代码清单 3-4

<script type="text/javascript">
var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");
ctx.beginPath();
ctx.strokeStyle = "#000000";
ctx.strokeRect(10,10,150,100);

ctx.translate(50,100);
ctx.beginPath();
ctx.strokeStyle = "#cccccc";
ctx.strokeRect(10,10,150,100);
</script>

运行效果如图3-4所示。

image

在代码清单3-4中的如下代码表示在水平方向上向右平移50,在竖直方向上向下平移100,图3-4中灰色矩形是平移之后的效果。

ctx.translate(50,100);

3.1.3 旋转
利用rotate函数可以实现图形的旋转功能。
其函数原型如下:

rotate (angle);

这里需要注意的是,传入rotate里的参数angle是弧度而不是角度。如果角度为angle,那么换算为弧度就是angle*Math.PI/180,具体实现方法如代码清单3-5所示。
代码清单 3-5

<script type="text/javascript">
var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");
ctx.beginPath();
ctx.strokeStyle = "#000000";
ctx.strokeRect(200,50,100,50);

ctx.rotate(45*Math.PI/180);
ctx.beginPath();
ctx.strokeStyle = "#ff0000";
ctx.strokeRect(200,50,100,50);
</script>

运行效果如图3-5所示。
在图3-5中灰色矩形是旋转45度后的图形,可以看到用rotate来实现旋转时,是以Canvas的起始坐标(0,0)为中心进行旋转的。如果要想让图形以自己为中心旋转,则需要使用3.1.2节中的translate函数,如代码清单3-6所示。

image

代码清单 3-6

<script type="text/javascript">
var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");
ctx.beginPath();
ctx.strokeStyle = "#000000";
ctx.strokeRect(200,50,100,50);

ctx.translate(250,75);
ctx.rotate(45*Math.PI/180);
ctx.translate(-250, -75);
ctx.beginPath();
ctx.strokeStyle = "#ff0000";
ctx.strokeRect(200,50,100,50);
</script>

运行效果如图3-6所示。

image

在代码清单3-6中,下面的代码表示先将Canvas的起始坐标向右移250,向下移75,即移至所画矩形的中心处;然后开始旋转45度;接着再将Canvas的起始坐标向左移250,向上移75,即移回到原来位置,这样就完成了图形以自己为中心的旋转。

ctx.translate(250,75);
ctx.rotate(45*Math.PI/180);
ctx.translate(-250, -75);

3.1.4 利用transform矩阵实现多样化的变形
上面分别讲了缩放、平移以及旋转的实现方法。其实所有这些变形都是可以通过变形矩阵transform来实现的。先来看看transform函数的定义。
transform函数的原型如下:

transform (a,b,c,d,e,f);

该函数的各个变量对应以下变换矩阵中相应位置的参数。
a c e
b d f
0 0 1
下面来说明如何使用这个变换矩阵来实现上面的各种变形。
1 . 缩放
假设原始坐标为(x,y),缩放后的坐标为(x1,y1),缩放的倍数分别为a和d,那么就有下列公式:

x1=a*x
y1=d*y

因此,我们得到了如下矩阵公式。

x1         a  0 0  x
y1     =      0 d 0  y
1         0 0 1  1

这样就可以用transform (a,0,0,d,0,0)来替换scale(a,d)了,如代码清单3-7所示。
代码清单 3-7

<script type="text/javascript">
var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");
ctx.beginPath();
ctx.strokeStyle = "#000000";
ctx.strokeRect(10,10,150,100);

ctx.transform(3,0,0,3,0,0); 
ctx.beginPath();
ctx.strokeStyle = "#cccccc";
ctx.strokeRect(10,10,150,100);
</script>

运行效果如图3-7所示。

image

可以看到,这里用代码清单3-7实现了和代码清单3-1一样的功能。

  1. 平移
    同样,假设原始坐标为(x,y),平移后的坐标为(x1,y1),在x轴和y轴的平移量分别为e和f,那么就有下列公式:
x1=x+e
y1=y+f

因此,我们得到如下矩阵公式。

x1         1 0 e  x    
    y1     =      0 1 f  y    
    1         0 0 1  1

这样就可以用transform (1,0,0,1,e,f)来替换translate (e,f)了,如代码清单3-8所示。
代码清单 3-8

<script type="text/javascript">
var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");
ctx.beginPath();
ctx.strokeStyle = "#000000";
ctx.strokeRect(10,10,150,100);

ctx.transform (1,0,0,1,50,100);
ctx.beginPath();
ctx.strokeStyle = "#cccccc";
ctx.strokeRect(10,10,150,100);
</script>

运行效果如图3-8所示。
image

可以看到,这里用代码清单3-8实现了和代码清单3-4一样的功能。
3 . 旋转
旋转对应的矩阵公式如下。

x1         cos     –sin      0        x
y1     =      sin     cos     0        y
1         0    0    1        1

现在可以用transform (cos , sin , –sin , cos ,0,0)来替换rotate ()了,如代码清单3-9所示。
代码清单 3-9

<script type="text/javascript">
var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");
ctx.beginPath();
ctx.strokeStyle = "#000000";
ctx.strokeRect(200,50,100,50);

ctx.transform (Math.cos(45*Math.PI/180),
    Math.sin(45*Math.PI/180),
    -Math.sin(45*Math.PI/180), 
    Math.cos(45*Math.PI/180),0,0);
ctx.beginPath();
ctx.strokeStyle = "#cccccc";
ctx.strokeRect(200,50,100,50);
</script>
</body>

运行效果如图3-9所示。
可以看到,这里用代码清单3-9实现了和代码清单3-5一样的功能。
这样,我们就用transform完成了所有变形。另外,变换矩阵也可以通过setTransform函数来实现,setTransform的参数与transform一样,不同的是,setTransform函数是先消去之前的transform变换,然后重新进行变换的。为了区分transform和setTransform,我们看看下面的例子,如代码清单3-10所示。

image

代码清单 3-10

<script type="text/javascript">
var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");
ctx.beginPath();
ctx.strokeStyle = "#000000";
ctx.strokeRect(200,50,100,50);

ctx.transform (Math.cos(5*Math.PI/180),
    Math.sin(5*Math.PI/180),
    -Math.sin(5*Math.PI/180), 
    Math.cos(5*Math.PI/180),0,0);
ctx.beginPath();
ctx.strokeStyle = "#cccccc";
ctx.strokeRect(200,50,100,50);

ctx.transform (Math.cos(10*Math.PI/180),
    Math.sin(10*Math.PI/180),
    -Math.sin(10*Math.PI/180), 
    Math.cos(10*Math.PI/180),0,0);
ctx.beginPath();
ctx.strokeStyle = "#999999";
ctx.strokeRect(200,50,100,50);
</script>

运行效果如图3-10所示。

image

在代码清单3-10中,首先旋转5度,然后再旋转10度,所以第二次旋转相当于旋转了15度。
下面再看看代码清单3-11中setTransform的使用方法。
代码清单 3-11

<script type="text/javascript">
var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");
ctx.beginPath();
ctx.strokeStyle = "#000000";
ctx.strokeRect(200,50,100,50);

ctx.transform (Math.cos(5*Math.PI/180),
    Math.sin(5*Math.PI/180),
    -Math.sin(5*Math.PI/180), 
    Math.cos(5*Math.PI/180),0,0);
ctx.beginPath();
ctx.strokeStyle = "#cccccc";
ctx.strokeRect(200,50,100,50);

ctx.setTransform (Math.cos(10*Math.PI/180),
    Math.sin(10*Math.PI/180),
    -Math.sin(10*Math.PI/180), 
    Math.cos(10*Math.PI/180),0,0);
ctx.beginPath();
ctx.strokeStyle = "#999999";
ctx.strokeRect(200,50,100,50);
</script>

运行效果如图3-11所示。
可以看到,第二次的旋转没有在第一次旋转的基础上进行,因为用setTransform实现旋转效果的时候,已经清除了上一个transform,所以只旋转了10度。
4 . 倾斜
接着来看一下如何实现图像的倾斜效果。首先看图3-12。
image

图中的3个点p0、p1、p2遵循以下矩形公式。

(p1.x–p0.x)/width    (p2.x–p0.x)/height    p0.x        
(p1.y–p0.y)/width    (p2.y–p0.y)/height    p0.y        
0    0    1        

将上面的矩形公式带入setTransform函数中,如代码清单3-12所示。
代码清单 3-12

<script type="text/javascript">
var c=document.getElementById('myCanvas');  
var ctx=c.getContext('2d');
ctx.setTransform(1,10/150,-40/100,1,40,10);  
ctx.rect(50,50,150,100);  
ctx.stroke();
</script>

运行效果如图3-13所示。
上述倾斜效果也可以根据另外3个点来实现,看图3-14。
image

图中的3个点p1、p2、p3遵循以下矩形公式。

(p3.x–p2.x)/width    (p3.x–p1.x)/height    p2.x        
(p3.y–p2.y)/width    (p3.y–p1.y)/height    p2.y        
0    0    1        

将上面的矩形公式代入setTransform函数中,如代码清单3-13所示。
代码清单 3-13

<script type="text/javascript">
var c=document.getElementById('myCanvas');  
var ctx=c.getContext('2d');
ctx.setTransform(130/150,-20/150,-20/100,80/100,0,0);
ctx.rect(50,50,150,100);  
ctx.stroke();
</script>

运行效果如图3-15所示。

image

5 . 图片的扭曲效果
根据以上所介绍的内容,已经可以做到倾斜、旋转、平移和缩放4种基本变形,如图3-16所示。

image

但是,对于非上述4种基本变形的特殊变形,就无法直接实现了。比如图3-17中的扭曲变形效果。
这时候,就需要通过多种变形组合来实现。先对图3-17进行分解,如图3-18所示。
此时分解出的两个图形可分别看作是两个基本变形的一部分,如图3-19所示。

image

image

image

这样一来就简单了,只需要实现两个倾斜的变形,然后利用clip函数将图片的一部分绘制出来,就可以完成图3-17中的效果了,如代码清单3-14所示。
代码清单 3-14

<script type="text/javascript">
var c=document.getElementById('myCanvas');
var ctx=c.getContext('2d');
var img = new Image();
img.src="face.jpg";
img.onload = function(){
    ctx.save();
    ctx.beginPath();
    ctx.moveTo(80,0);
    ctx.lineTo(320,40);
    ctx.lineTo(0,200);
    ctx.closePath();
    ctx.clip();
    ctx.setTransform((320-80)/240,40/240,-80/240,200/240,80,0);
    ctx.drawImage(img,0,0);
    ctx.restore();
    
    ctx.save();
    ctx.beginPath();
    ctx.moveTo(320,40);
    ctx.lineTo(0,200);
    ctx.lineTo(200,150);
    ctx.closePath();
    ctx.clip();
    ctx.setTransform(200/240,(150-200)/240,(200-320)/240,(150-40)/240,0,200);
    ctx.drawImage(img,0,0-240);
    ctx.restore();
};
</script>

运行上面的代码,就可以得到图3-17的效果了。
解释一下代码清单3-14。首先看一下左半边图形的绘制。

ctx.beginPath();
ctx.moveTo(80,0);
ctx.lineTo(320,40);
ctx.lineTo(0,200);
ctx.closePath();
ctx.clip();

上面这段代码是以(80,0)、(320,40)和(0,200) 3个点为顶点绘制一个三角形,然后利用clip函数将这个三角形作为绘图的可视区域。

ctx.setTransform(200/240,(150-200)/240,(200-320)/240,(150-40)/240,0,200);

上面的代码是以刚才的三角形的3个顶点来进行倾斜变形的,这个变形的原理已经在代码清单3-12中讲过了。

ctx.drawImage(img,0,0);

上面的代码是绘制图片,因为绘图的可视区域只是一个三角形,所以绘制完的图片只有一部分。
右半边图形的绘制思路和左半边是一样的。同时,两次绘图都加上了save()函数和restore()函数,这是为了让两次变形和绘图互不干涉。

相关文章
|
1月前
|
移动开发 前端开发 JavaScript
纯JavaScript实现HTML5 Canvas六种特效滤镜
纯JavaScript实现HTML5 Canvas六种特效滤镜
41 6
|
1月前
|
移动开发 前端开发 API
HTML5 Canvas渐进填充与透明
HTML5 Canvas渐进填充与透明
26 7
|
1月前
|
移动开发 前端开发 JavaScript
HTML5 Canvas鼠标与键盘事件
HTML5 Canvas鼠标与键盘事件
34 5
|
1月前
|
移动开发 前端开发 JavaScript
HTML5 Canvas平移,放缩,旋转演示
HTML5 Canvas平移,放缩,旋转演示
26 4
|
1月前
|
移动开发 前端开发 API
HTML5 Canvas 填充与描边(Fill And Stroke)
HTML5 Canvas 填充与描边(Fill And Stroke)
54 3
|
1月前
|
移动开发 前端开发 JavaScript
纯HTML5 Canvas实现的饼图
纯HTML5 Canvas实现的饼图
21 6
|
1月前
|
移动开发 前端开发 JavaScript
HTML5 Canvas动画效果演示
HTML5 Canvas动画效果演示
29 5
|
1月前
|
移动开发 缓存 前端开发
提高HTML5 Canvas性能的技巧
提高HTML5 Canvas性能的技巧
19 5
|
1月前
|
移动开发 前端开发 JavaScript
将HTML5 Canvas的内容保存为图片
将HTML5 Canvas的内容保存为图片
19 5
|
1月前
|
移动开发 前端开发 HTML5
HTML5 Canvas阴影用法演示
HTML5 Canvas阴影用法演示
37 4