本节书摘来自异步社区《HTML5游戏编程核心技术与实战》一书中的第2章,第2.3节,作者: 向峰 更多章节内容可以访问云栖社区“异步社区”公众号查看。

2.3 图像API

除了绘制常用的图形以外,canvas提供了一系列的API能够对图像进行操作,常见的图像API有以下3个方法。

drawImage (image, dx, dy):把image图像绘制到画布上(dx, dy)坐标位置。
drawImage (image, dx, dy, w, h):把image图像绘制到画布上(dx, dy)坐标位置,图像的宽度是w,高度是h。
drawImage (image, sx, sy, sw, sh, dx, dy, dw, dh):截取image图像以(sx, sy)为左上角坐标,宽度为sw,高度为sh的一块矩形区域绘制到画布上(dx, dy)坐标位置,图像宽度是dw,高度是dh。
其中image可以是htmlImageElement元素、htmlcanvasElement元素、htmlVideoElement元素,htmlVideoElement元素是HTML5中新增加的video视频播放元素。


https://yqfile.alicdn.com/72e8aa99f7730abe9609f15c55afa6a503f12e7f.png" >

图2-14显示了drawImage中源图像和目标canvas之间的关系。

采用后面两种方式进行绘制的时候可以实现图像的放大和缩小。
**
2.3.1 使用canvas绘制图像**
先看一个简单的例子,我们把< image />标签中的图像显示到一个canvas中。

<body><h2>图片显示</h2><img src="img/t1.jpg" id="img1" /><input type="button" id="btnCopy" value="拷贝图片" /><br><canvas id="can" width="400" height="300" ></canvas>
</body>
<script>function $(id){return document.getElementById(id);}$("btnCopy").onclick = function(){//获取canvas的上下文var ctx = $("can").getContext("2d");//设置canvas的大小$("can").width = $("img1").width;$("can").height = $("img1").height;ctx.drawImage($("img1"), 0, 0);}
</script>

代码中有一个图片元素、一个按钮和一个canvas元素,当点击按钮的时候首先获取了id为img1的图片的宽度和高度,然后改变canvas的大小,最后使用drawImage方法把img的图片绘制到canvas元素中,使用drawImage把img1绘制到canvas坐标(0, 0)的位置,大小和原图像一样大。

2.3.2 案例:放大镜
现在,我们可以借助drawImage方法实现一个简单的放大镜效果,效果如图2-15所示。

在这个放大镜效果中,我们可以在任意一张图片上点击,在该位置就会出现一个圆形的放大镜。实现原理也比较简单,首先,需要获取鼠标当前的位置,然后根据放大镜的尺寸,截取图片中相应大小的图片,最后通过drawImage方法把图片中的部分绘制到canvas上面。

这里涉及一个问题,放大镜是圆形的,但实际上如果直接通过canvas绘制,将最终以矩形的方式显示。这里,我们可以使用剪裁的方式进行绘制,所谓剪裁,就是在canvas中指定一块区域,在这个区域之内的都会显示,之外的一律不显示,所以如果我们把剪裁区域设置成圆形就可以了。canvas中使用context.clip()方法定义一个画布的剪裁路径,方法没有任何参数,它会使用当前的路径作为一个剪裁的区域,默认情况下,整个canvas本身就是一个剪裁路径。如 图2-16所示,图中定义了一个圆形的剪裁区域,在圆形之外的区域将不会显示。


https://yqfile.alicdn.com/35d27e7f6c5a83d54d35ecf5cc2619fa90c25ad1.png" >

好了,有了以上的基本知识,现在可以开始制作一个放大镜了,具体代码如下:

<body><h2>放大镜</h2><img src="img/tx1.jpg" id="s1"></img>
</body>
<script>//根据编号获取对象function $(id){return document.getElementById(id);} //定义放大镜var Glass = {bind:function(imgId, zRat){var self = this;this.canvas = document.createElement("canvas");   this.canvas.style.display="none";this.canvas.style.position="absolute";this.ctx = this.canvas.getContext("2d");this.canvas.width = 100;this.canvas.height = 100;this.hEle = $(imgId);//设置放大比例this.zRat = zRat|2;//设置鼠标按下事件     document.body.appendChild(this.canvas);document.body.onmousedown=function(e){if(e.srcElement.id==imgId){e.preventDefault();draw(e);//定义绘制方法function draw(e){//获取鼠标位置var x = e.pageX, y = e.pageY;//获取图片相对位置var exOff = x-self.hEle.offsetLeft, eyOff = y-self.hEle.offsetTop;       //设置获取图片周围长度var rLen = 50/self.zRat;self.copyImg(exOff-rLen, eyOff-rLen, rLen*2, rLen*2);self.show(x-50, y-50);}document.body.onmousemove = draw;document.body.onmouseup = function(){self.hide();document.body.onmousemove = null;};}                   };},copyImg:function(x, y, w, h){    this.ctx.arc(50, 50, 50, 0, Math.PI*2, true);//设置路径剪裁形成圆形this.ctx.clip();this.ctx.drawImage(this.hEle, x, y, w, h, 0, 0, 100, 100);},show:function(x, y){this.canvas.style.display = "block";this.canvas.style.pixelLeft = x;    this.canvas.style.pixelTop = y;    },hide:function(){this.canvas.style.display = "none";}}; Glass.bind("s1");
</script>

除了实现静态的效果外,还可以通过drawImage实现动画效果。

2.3.3 案例:帧动画实现
来看一个比较复杂的例子,该例子中实现了一个简单的人物动画,先简单了解一下动画的原理。通常我们看到的动画称为逐帧动画,它是利用人眼睛视觉暂留的原理,即物体被移动后其形象在人眼视网膜上还可有约1秒的停留。利用这个原理,我们在一秒钟内如果连续放映20张静态的图片,这样就形成了动画的效果,当然20是最基本的要求,如果流畅的话至少要30帧/秒,也就是说每秒钟放30张静态图片,每一帧图片称为一帧,在第4章的制作游戏引擎中会详细介绍动画机制。

本例子中我们有3张人物行走的图片,这3张图片构成了人物行走的过程,为了提高效率通常情况下制作动画的时候,会把人物的动作放在一张图片中,如图2-17所示(该图截取自游戏《超级玛丽》)。


这张图片包含了玛丽行走的动画,一共由3帧组成,我们在canvas上循环绘制这三张图片就形成了玛丽行走的动画,代码如下:

<body><h2>图片动画</h2><img id="img1" src="img/mr.png" /><input id="btnGO" type="button" value="开始" /><br><canvas id="c1" width="320" height="200" ></canvas><br> 
</body>
<script>//是否开始动画var isAnimStart = false, animHandle = null;//动画句柄//保存每帧动画起始坐标,本例图片共有3帧var frames=[[0, 0],[32, 0],[64, 0]];//定义每帧图像的宽度和高度var fWidth = 32, fHeight = 32;function $(id){return document.getElementById(id);}//开始function init(){//注册GO按钮事件$("btnGO").onclick=function(){//如果没开始动画,则开始动画if(!isAnimStart){//获取canvas上下文var ctx = $("c1").getContext("2d");//设置当前帧序号var fIndex = 0;//找到canvas的中点var cX = 160, cY = 100;animHandle = setInterval(function(){//先清空画布ctx.clearRect(0, 0, 320, 200);//绘制当前帧ctx.drawImage(img1, frames[fIndex][0], frames[fIndex][1], fWidth, fHeight, cX-64, cY-64, fWidth*4, fHeight*4);//计算下一帧fIndex++;if(fIndex>=frames.length){fIndex = 0;}}, 100)$("btnGO").value = "停止";isAnimStart = true; }else{$("btnGO").value = "开始";clearInterval(animHandle);isAnimStart = false;}}}init();
</script>

来看看代码功能,首先定义了几个变量,isAnimStart标记是否已经开始播放动画标记,用来切换播放和停止动画。animHandle保存定时器的句柄,可以通过它关闭定时器,定义frames数组变量记录图片中3个动作帧左上角的起始坐标,在绘制动画的时候,需要根据这个坐标来截取图片中的每帧图像。fWidth和fHeight分别表示每帧图片的宽度和高度,本例中使用的玛丽图像帧是32×32大小。然后定义init初始化方法,在该方法中绑定了开始按钮的事件,为了实现动画的播放,使用了serInterval方法,该方法每100毫秒执行一次,每次读取frames中的一帧图像,并把它显示在画布上,其核心代码是:

//先清空画布
ctx.clearRect(0, 0, 320, 200);
//绘制当前帧
ctx.drawImage(img1, frames[fIndex][0], frames[fIndex][1], fWidth,fHeight, cX-64,  cY-64, fWidth*4, fHeight*4);

这段代码首先使用ctx.clearRect方法清空整个画布,因为在定时器动画中需要每100毫秒更新画布中的内容,那么每次更新时,需要先把上个画面清除掉,然后再进行绘制,否则会把几张动画重叠起来。然后使用了ctx.drawImage方法,把img1图片中的内容复制到canvas上面,复制的时候第1个参数是选取的图片,第2~5的参数分别对应着img1图片上的一块矩形区域,最后4个参数对应着canvas上面的一块矩形区域,实际上就是把图片上的某一矩形区域部分绘制到canvas上面,由于画到canvas上面的宽度和高度分别是fWidth×4和fHeight×4,实际上也就是把原图像放大了4倍。

除了直接对图片元素操作外,canvas还提供了直接对像素元素进行处理的API。

2.3.4 像素操作
canvas另外一个非常强大的功能就是可以对图像中的任一个像素进行处理。我们知道图像实际上是由很多像素点组成,一幅宽度为320,高度200的图片是由320×200=64 000个像素点构成,每个像素由red、green、blue三种颜色组成。

canvas提供了以下API让我们可以进行像素操作。

getImageData (sx, sy, sw, sh):获取canvas上以(sx, sy)为左上角,宽度为sw,高度为sh的一块矩形区域的像素数据。通过getImageData获取到了imageData对象,该对象有以下3个属性。
width:每行的像素数量。
height:每列的像素数量。
data:存有从canvas中获取的每个像素的RGBA的值,该数组为每个像素保存了四个值,分别是红色、绿色、蓝色和alpha透明度,每个值在0~255之间,数组填充的数据是从上到下,从左到右,比如imgData.data[0]~imgData.data[3]就保存了canvas图像中左上角第一个像素点的RGBA的数据,imgData.data[4]~imgData.data[7]保存了canvas图像中左上角第二个像素点的RGBA的数据,依次类推,从左到右,从上到下。
createImageData (sw, sh):创建一个宽度为sw,高度为sh的imageData对象,该对象中所有的像素都是黑色的。
createImageData (imageData):创建一个imageData对象的副本,像素值和imageData的一致。
putImageData (imageData, dx, dy, [dirtyX, dirtyY, dirtyWidth, dirtyHeight]):在绘图画布上绘制给定的ImageData对象。假如脏矩形被提供,则只有在脏矩形上面的像素被绘制。本方法对全局透明、阴影和全局组合等属性均忽略。
data数组中的数据可以获取也可以设置,我们可以把data中的数据进行修改后重新绘制达到修改图像的目的。

利用可以对像素操作的特性,我们可以完成一些简单的图像处理效果,类似于Photoshop中的滤镜效果,如转成灰度图、浮雕效果等,接下来看看相关的例子。

2.3.5 案例:转换灰度图
现在来看一个简单的像素操作的例子,该例子中会把任意一张彩色图片转换成灰度图片,如图2-18所示。


彩色图像要转换成灰度图,需要对图中的每一个像素点进行处理,我们可以通过getImage Data()方法获取每个点的像素值,然后把该点的色彩转成灰度。彩色转灰度的算法很多,这里,我们采用以下算法:

灰度值=(R×30 + G×59 + B×11 + 50) / 100

具体实现代码如下:

<body><h2>像素操作</h2><img id="img1" src="img/t1.jpg" /><input id="btnGO" type="button" value="转成灰度图" /><br><canvas id="c1" width="320" height="200" ></canvas><br> 
</body>
<script>function $(id){return document.getElementById(id);}//开始function init(){//注册GO按钮事件$("btnGO").onclick=function(){c1.width = img1.width;c1.height = img1.height;//先把image绘制到canvas上var ctx = c1.getContext("2d");ctx.drawImage(img1, 0, 0, c1.width, c1.height);//获取像素数据var imgData = ctx.getImageData(0, 0, c1.width, c1.height);for(var i = 0;i<imgData.data.length;i+=4){//获取RGB像素值var r = imgData.data[i],g = imgData.data[i+1],b = imgData.data[i+2];//计算灰度值,常用公式 Gray = (R*30 + G*59 + B*11 + 50) / 100var gray = (r*30+g*59+b*11+50)*0.01;imgData.data[i] = gray;imgData.data[i+1] = gray;imgData.data[i+2] = gray;}//最后把imgdata数据绘制到canvas中ctx.putImageData(imgData, 0, 0);}}
init();

代码先通过contex.drawImage把图像绘制到canvas上,需要注意的是,只有canvas上有图像了才能通过getImageData获取canvas的图像数据,然后定义imgData变量得到canvas的图像数据,接着通过循环imgData数据,修改canvas图像中的每个像素数据。因为每个图像数据由4个值构成,所以循环data数据每次加4,然后接着获取每个像素的RGB值,然后通过公式转成灰度。

把彩色信息转成灰度信息后,最后重新设置每个像素的颜色值,就完成了彩色图到灰度图的转变。我们虽然改变了图像的像素值,但是还不能让图像立刻变成我们想要的样子,还需要重新把像素的数据绘制到canvas中,所以我们使用了putImageData方法。

这样就完成了整个的图像转变过程。

2.3.6 案例:浮雕效果
接下来,来实现一个浮雕效果,浮雕效果如图2-19所示。


https://yqfile.alicdn.com/c730b1328421c27c2b165b5fb6e416eef0c84ca9.png" >

浮雕效果的实现原理也有很多种,常用的算法是把每个点周围的8个点和一个转换矩阵进行卷积操作,得到的值作为该点的新色彩。但计算量过大,这里采用一种相对简单的算法,该算法是这样的,对于任一点的像素来说,新的色彩值等于该点的色彩和右边像素的色彩值相减,然后加上128。

具体实现如下:

<body><h2>像素操作</h2><img id="img1" src="img/t1.jpg" /><input id="btnGO" type="button" value="转成浮雕图" /><canvas id="c1" width="320" height="200" ></canvas><br> 
</body>
<script>function $(id){return document.getElementById(id);}//开始function init(){//注册GO按钮事件$("btnGO").onclick=function(){c1.width = img1.width;c1.height = img1.height;//先把image绘制到canvas上var ctx = c1.getContext("2d");ctx.drawImage(img1, 0, 0, c1.width, c1.height);//获取像素数据var imgData = ctx.getImageData(0, 0, c1.width, c1.height);var iData = imgData.data;for(var i = 0;i<img1.height-1;i++){for(var j = 0;j<img1.width;j++){//获取像素在dataImage起始位置var start = (i*img1.width+j)<<2;         var r = iData[start]-iData[start+4]+128,g = iData[start+1]-iData[start+5]+128,b = iData[start+2]-iData[start+6]+128;//越界处理r = (r<0)?0:(r>255)?255:r;g = (g<0)?0:(g>255)?255:g;b = (b<0)?0:(b>255)?255:b;//转灰度图var g=(r*30+g*59+b*11+50)*0.01;iData[start] = g;iData[start+1] = g;iData[start+2] = g;}}     //最后把imgdata数据绘制到canvas中ctx.putImageData(imgData, 0, 0);}}init();
</script>

需要注意的是,在得到新的像素点色彩值之后,需要进行越界处理,并转成灰度色彩,否则,该像素点还有可能是彩色点。

接下来,来看看坐标变换相关的一些知识。

《HTML5游戏编程核心技术与实战》——2.3 图像API相关推荐

  1. 《HTML5游戏编程核心技术与实战》——2.8 小结

    本节书摘来自异步社区<HTML5游戏编程核心技术与实战>一书中的第2章,第2.8节,作者: 向峰 更多章节内容可以访问云栖社区"异步社区"公众号查看. 2.8 小结 本 ...

  2. 《HTML5游戏编程核心技术与实战》一2.6 其他全局属性

    本节书摘来异步社区<HTML5游戏编程核心技术与实战>一书中的第2章,第2.6节,作者: 向峰 责编: 杨海玲,更多章节内容可以访问云栖社区"异步社区"公众号查看. 2 ...

  3. 《HTML5游戏编程核心技术与实战》——2.5 绘制文字

    本节书摘来自异步社区<HTML5游戏编程核心技术与实战>一书中的第2章,第2.5节,作者: 向峰 更多章节内容可以访问云栖社区"异步社区"公众号查看. 2.5 绘制文字 ...

  4. html5游戏编程核心技术与实战目录

    html5游戏编程核心技术与实战目录 概述 将书上的代码手工敲一遍,另外由于书出版比较早,有些地方的写法已经过时了,做了一些修改.比如var 改为了let,div上面的属性 align="c ...

  5. 《HTML5游戏编程核心技术与实战》一1.1 网页游戏和HTML5

    本节书摘来异步社区<HTML5游戏编程核心技术与实战>一书中的第1章,第1.1节,作者: 向峰 责编: 杨海玲,更多章节内容可以访问云栖社区"异步社区"公众号查看. 1 ...

  6. 《HTML5游戏编程核心技术与实战》——第1章 游戏和HTML5初探1.1 网页游戏和HTML5...

    本节书摘来自异步社区<HTML5游戏编程核心技术与实战>一书中的第1章,第1.1节,作者: 向峰 更多章节内容可以访问云栖社区"异步社区"公众号查看. 第1章 游戏和H ...

  7. 《HTML5游戏编程核心技术与实战》一2.3 图像API

    图片动画

  8. 《HTML5 2D游戏编程核心技术》——第1章,第1.3节特别功能

    本节书摘来自华章出版社<HTML5 2D游戏编程核心技术>一书中的第1章,第1.3节特别功能,作者[美] 戴维·吉尔里,更多章节内容可以访问云栖社区"华章计算机"公众号 ...

  9. 《HTML5 2D游戏编程核心技术》——第2章,第2.3节使用CSS背景

    **本节书摘来自华章出版社<HTML5 2D游戏编程核心技术>一书中的第2章,第2.3节,作者[美] 戴维·吉尔里,更多章节内容可以访问云栖社区"华章计算机"公众号查看 ...

  10. 《HTML5 2D游戏编程核心技术》——第1章,第1.1节Snail Bait游戏

    本节书摘来自华章出版社<HTML5 2D游戏编程核心技术>一书中的第1章,第1.1节,作者[美] 戴维·吉尔里,更多章节内容可以访问云栖社区"华章计算机"公众号查看. ...

最新文章

  1. eclipse或者myeclipse的代码提示功能
  2. 在SQL Server 2008中配置文件流(FILESTREAM)
  3. eclipse 添加 server library
  4. Excel加密的Sheet如何hack
  5. 帧大小超过交换机mtu_交换机的三种交换方式
  6. python的messagebox的用法_PyQt5快速上手基础篇5-messagebox用法
  7. 轉載:2006中国大陆企业慈善捐赠100榜
  8. WebStorm中TODO的作用
  9. 捋一捋Python中的List(下)
  10. CH340g的使用说明
  11. go老鸟也会经常出现 并发channal问题引起的协程泄漏
  12. Error: watch task has to be a function(optionally generated by using gulp.parallel or gulp. series)
  13. 探究from表单的身份证号码的校验逻辑 javascript
  14. 随笔6 崇拜轩昂神有感
  15. flutter实现可缩放可拖拽双击放大的图片功能
  16. redhatEL7下的dns服务搭建以及ddns配置
  17. 手机关机不拔电池也能被定位吗?
  18. ACM-ICPC 2018 徐州赛区网络预赛 I. Characters with Hash
  19. JavaScript 实现碰壁反弹
  20. 什么是引流脚本,怎么选引流脚本效果更好?

热门文章

  1. [转]Java程序员们最常犯的10个错误
  2. Grep与web漏洞挖掘转
  3. pd虚拟机提示 “无法连接到 Parallels 服务” 怎么解决?
  4. PDFsam Basic for mac(合并拆分PDF文档)支持m1
  5. 【Flutter】Dart的方法中的可选参数、方法作为参数传递
  6. linux 一个读写锁的使用异常导致的故障
  7. Linux 最常用 的150 个命令汇总
  8. webpack4入门笔记——loader
  9. 磁盘IO单线程顺序写时最快的,如果多线程写,磁盘的磁头要不断重新寻址,所以写入速度反而会慢...
  10. MVC 如何设定默认默认路由为指定的Area下的某个action(笔记)