HTML5- Canvas入门(五)
今天要介绍的是canvas对图形对象的操作,包括图像、视频绘制,和操作像素对象的方法。

图片/视频的绘制
在canvas中,我们可以通过 drawImage() 的方法来绘制图片或视频文件,其语法为:
ctx.drawImage( img, clip_x, clip_y, clip_w, clip_h, x, y, width, height );
其中红色的参数为可选项,它们的含义如下:

⑴ 我们先来看下最简单的形式 ctx.drawImage(img, x, y):
<canvas id="myCanvas" width="300" height="300" style="border:solid 1px #CCC;">
您的浏览器不支持canvas,建议使用最新版的Chrome
</canvas> <script>
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
var img = new Image();
img.src = "http://images.cnblogs.com/cnblogs_com/vajoy/558869/o_avatar.jpg";
img.onload = function(){
ctx.drawImage(img,30,30); //在画布坐标(30,30)的位置绘制图片
}
</script>
注意如同我们在第一章说讲到的,应当等图片onload之后才执行绘图代码,防止代码在图片加载到之前就执行。效果如下:

⑵ 我们也可以通过添加 width 和 height 参数来缩放图片:
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
var img = new Image();
img.src = "http://images.cnblogs.com/cnblogs_com/vajoy/558869/o_avatar.jpg";
img.onload = function(){
ctx.drawImage(img,30,30,250,150); //在画布坐标(30,30)的位置绘制一张宽度为250,高度为150的图片
}

⑶ 我们把裁剪图片的参数 clip_x, clip_y, clip_w, clip_h 也都加上:
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
var img = new Image();
img.src = "http://images.cnblogs.com/cnblogs_com/vajoy/558869/o_avatar.jpg";
img.onload = function(){
ctx.drawImage(img,10,20,300,300,30,30,250,150); //在画布坐标(30,30)的位置绘制一张宽度为250、高度150的图片,这种图片是在img上坐标为(10,20)的位置所裁剪出来的宽高均为300的区域
}

注意这里被拉伸的图片已经不再是一开始的那张原始图了,而是原始图在其坐标(10,20)处开始裁剪到的宽高均为300的区域,也就是把这个裁剪到的区域,再伸缩为宽250、高150。
把参数全部用上虽然感觉有点繁琐,但它可以实现像css sprite的效果,从而有效减少图片文件请求数量,进而减少我们要写img.onload=.... 的次数。

说到裁剪我们顺便说说另一个canvas方法 clip() ,它是更地道的“裁剪”方法,在使用它之前需要绘制一个闭合路径(比如一个rect),使用clip()之后的绘制语句所绘制的对象只能显示被裁剪的区域(就一开始定义的那个闭合路径里的区域,类似PS的蒙板、Flash里的遮罩层):
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.rect(60,60,100,100); //绘制裁剪区域(一个矩形)
ctx.clip(); //设置上一个闭合路径为裁剪蒙板
var img = new Image();
img.src = "http://images.cnblogs.com/cnblogs_com/vajoy/558869/o_avatar.jpg";
img.onload = function(){
ctx.drawImage(img,10,20);
}


我们说回一开始讲的 drawImage() 方法,它有一个蛮屌的功能——获取和绘制视频当前图像,这里提供下3wschool的案例。
利用这个功能,再配合ImageData对象的方法,我们甚至可以用来替换绿屏视频的绿色背景。至于什么是ImageData对象,这是我们接下来要讲的地方。

ImageData你可以理解为“含像素数据的图形对象”,“像素数据”指的是该图形对象上的每一个有序的像素的数据,每个像素都有它对应的颜色数据(RGBA值)。
我们可以通过 createImageData(width,height) 方法来创建一个ImageData对象,然后通过 putImageData(imgData,x,y) 方法把ImageData对象放到画布上:
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
var imgData=ctx.createImageData(200,100); //创建一个宽为200,高为100的ImageData对象
ctx.putImageData(imgData,50,60); //将上述创建的ImageData对象放到画布坐标(50,60)的位置

运行上述代码,不会有任何图形显示出来,因为我们仅仅创建了一个没有任何数据的ImageData对象。如何给ImageData对象赋予像素数据、定义其每一点像素的颜色呢?处理这问题要用到ImageData对象的 .data 属性。
我们要知道,一个图形对象上的每一点像素都是从上到下一行一行(每一行里又是从左到右)有序地排列着的,而每一个像素又有四个数值(RGBA)表示它的颜色。
比如下方有一个非常简单的图形对象(假设我把它放大了75倍,方便查看),它一共只有四个像素点,这四个像素点的RGBA数值分别是(255,255,0,255)、(0,255,64,255)、(43,149,255,255)、(236,103,100,51) :

那么这个图形对象的“像素数据”可以看为一个数组: [255,255,0,255,0,255,64,255,43,149,255,255,236,103,100,51]
也就是把四个像素的RGBA数据依次拼起来。当然这里只是一个非常简单的例子,常规的图像可能有几千几万个像素,但它们的像素数据都遵循这种存储方式。
而ImageData对象的 .data 属性正是返回这么一个存储像素数据的数组(没错就是数组,故有length属性)。我们可以这样进一步完善上方的代码:
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
var imgData=ctx.createImageData(200,100);
for (var i=0;i<imgData.data.length;i+=4) //遍历ImageData对象的每一个像素点,并给它们上色
{
imgData.data[i+0]=255;
imgData.data[i+1]=100;
imgData.data[i+2]=0;
imgData.data[i+3]=255;
}
ctx.putImageData(imgData,50,60);
此处我们给该ImageData对象的每一个像素都赋值了RGBA(255,100,0,255)的颜色,执行效果如下:

我们试着不要绘制纯色的ImageData对象,来个多彩的:
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
var imgData=ctx.createImageData(200,100);
for (var i=0,t=255;i<imgData.data.length;i+=4) //遍历ImageData对象的每一个像素点,并给它们上色
{
if(t<=0) t=255;
imgData.data[i+0]=255-t;
imgData.data[i+1]=t;
imgData.data[i+2]=255-t;
imgData.data[i+3]=255;
--t;
}
ctx.putImageData(imgData,50,60);
效果如下:


其实 putImageData() 方法还有四个可选参数,可以用来裁剪ImageData对象上的指定区域。其全部参数为:
ctx.putImageData( imgData, x, y, clip_X, clip_Y, clip_Width, clip_Height);
clip_X,clip_Y分别表示相对于ImageData对象的裁剪起始点坐标,clip_Width, clip_Height表示要裁剪的矩形区域宽高。例如上面的例子我们可以稍微裁剪一下,裁剪成正方形吧:
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
var imgData=ctx.createImageData(200,100);
for (var i=0,t=255;i<imgData.data.length;i+=4)
{
if(t<=0) t=255;
imgData.data[i+0]=255-t;
imgData.data[i+1]=t;
imgData.data[i+2]=255-t;
imgData.data[i+3]=255;
--t;
}
ctx.putImageData(imgData,60,60,50,0,100,100); //裁剪imgData上坐标为(50,0)且宽高均为100px的矩形区域,并在画布(60,60)的坐标上画出来


每一个ImageData对象都有其 width 和 height 属性,对应其宽度和高度:
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
var imgData=ctx.createImageData(200,100);
for (var i=0;i<imgData.data.length;i+=4)
{
imgData.data[i+0]=255;
imgData.data[i+1]=100;
imgData.data[i+2]=255;
imgData.data[i+3]=255;
}
ctx.putImageData(imgData,50,60);
console.log("宽度是" + imgData.width + ",高度是" + imgData.height ); //输出其宽高

另外介绍下获取已有ImageData对象的两个方式,首先是直接用 createImageData( imgData ) 的方式来获取已有的ImageData对象的尺寸,注意这里只会获取其尺寸,不会把已有对象的像素数据也复制了:
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
var imgData=ctx.createImageData(200,100);
for (var i=0;i<imgData.data.length;i+=4)
{
imgData.data[i+0]=255;
imgData.data[i+1]=100;
imgData.data[i+2]=255;
imgData.data[i+3]=255;
}
ctx.putImageData(imgData,50,10);
var imgData2=ctx.createImageData(imgData); //新建一个尺寸与已有的imgData一致的新ImageData对象imgData2,注意是不会复制其像素数据的
for (var i=0;i<imgData2.data.length;i+=4) //给imgData2上色
{
imgData2.data[i+0]=155;
imgData2.data[i+1]=200;
imgData2.data[i+2]=155;
imgData2.data[i+3]=155;
}
ctx.putImageData(imgData2,50,160);


另一种方法才算是地道的获取、复制已有ImageData对象的方法,即 getImageData() 方法,该方法返回一个 ImageData 对象,此对象拷贝了画布指定矩形区域的像素数据,其语法如下:
var newImgData=ctx.getImageData( x, y, width, height );
其中参数 x,y 分别表示要从画布上开始复制的起始点坐标,width,height 分别表示要复制矩形区域的宽度和高度。我们来个示例:
<body>
<img id="img" src="http://images.cnblogs.com/cnblogs_com/vajoy/558869/o_avatar.jpg" />
<canvas id="myCanvas" width="300" height="600" style="border:solid 1px #CCC;">
您的浏览器不支持canvas,建议使用最新版的Chrome
</canvas> <script>
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
var img = document.getElementById("img");
img.onload = function(){
ctx.drawImage(img,10,10);
var imgData=ctx.getImageData(0,0,c.width,c.height);
ctx.putImageData(imgData,0,300);
}
</script>
</body>
执行上述代码时发现
var imgData=ctx.getImageData(0,0,c.width,c.height);
ctx.putImageData(imgData,0,300);
这两句没有起任何作用,且Chrome报错“Uncaught SecurityError: Failed to execute 'getImageData' on 'CanvasRenderingContext2D': The canvas has been tainted by cross-origin data.”:

这是何处导致的问题?我们的代码写错或写漏了什么么?或者图片不能作为ImageData对象来获取?
其实不然,我们的代码没有特别的错误,而图片或者视频文件都可以算作ImageData对象,之所以会发送这个错误,是因为我们在canvas上放置了一张跨域的图片。
一旦canvas发现你绘制了一张跨域的图片时,它就会认为此时的画布是"tainted"、被污染的,从而不允许你操作该图片的像素,从而防止多种类型的XSS/CSRF攻击。
对于此问题的详细描述可以查看这里,而解决此问题的办法是在服务器的环境下来运行代码(当然图片也要放到项目目录下作为本地文件)。
我们使用tomcat/IIS/wamp等服务器来运行我们的项目,便可成功执行、得到我们想要的效果:


在上面代码的基础上,我们可以来执行一个有趣的效果,它类似于制图软件中将一张图片颜色“取反”,也就是说假如图片上某一点像素颜色是RGBA(255,0,100,255),取反后该像素的RGBA变为(0,255,155,255)。注意透明度Alpha是保持原值的。我们可以这样写代码:
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
var img = document.getElementById("img");
img.onload = function(){
ctx.drawImage(img,10,10);
var imgData=ctx.getImageData(0,0,c.width,c.height);
for (i=0; i<imgData.width*imgData.height*4;i+=4)
{
imgData.data[i]=255-imgData.data[i];
imgData.data[i+1]=255-imgData.data[i+1];
imgData.data[i+2]=255-imgData.data[i+2];
imgData.data[i+3]=255;
}
ctx.putImageData(imgData,0,300);
}
效果如下:


关于图像对象操作的介绍我们就讲到这,最后来个有趣也实用的应用。还记得我们在前面提到的,可以利用 drawImage 和 ImageData 的方法来替换屏幕的绿色背景么?
在MSDN有这么一段介绍:
从两个视频中读写像素到另一个视频中所需的代码要求使用两个视频、两个画布和一个最终画布。一次捕捉视频上的一帧,然后绘制到两个单独的画布上。这样允许读回数据。
其中画布1和画布2分别用来绘制两个视频当前帧的画面(注意这俩个画布我们设其样式visibility:hidden,即不可见):
ctxSource1.drawImage(video1, 0, 0, videoWidth, videoHeight);
ctxSource2.drawImage(video2, 0, 0, videoWidth, videoHeight);
然后我们可以轻松从这两个画布已绘制出来的图像并转为ImageData对象:
currentFrameSource1 = ctxSource1.getImageData(0, 0, videoWidth, videoHeight);
currentFrameSource2 = ctxSource2.getImageData(0, 0, videoWidth, videoHeight);
最后从浏览绿屏的像素数组中搜索绿色像素,如果找到,代码将用背景场景中的像素替换所有绿色像素:
for (var i = 0; i < n; i++)
{
// Grab the RBG for each pixel:
r = currentFrameSource1.data[i * 4 + 0];
g = currentFrameSource1.data[i * 4 + 1];
b = currentFrameSource1.data[i * 4 + 2]; // If this seems like a green pixel replace it:
if ( (r >= 0 && r <= 59) && (g >= 74 && g <= 144) && (b >= 0 && b <= 56) ) // Target green is (24, 109, 21), so look around those values.
{
pixelIndex = i * 4;
currentFrameSource1.data[pixelIndex] = currentFrameSource2.data[pixelIndex];
currentFrameSource1.data[pixelIndex + 1] = currentFrameSource2.data[pixelIndex + 1];
currentFrameSource1.data[pixelIndex + 2] = currentFrameSource2.data[pixelIndex + 2];
currentFrameSource1.data[pixelIndex + 3] = currentFrameSource2.data[pixelIndex + 3];
}
}
MSDN还专门提供了一个实例(点我查看),查看该页面源码即可获得全部代码,有兴趣的朋友可以研究下。

本章就讲到这里,下一章将介绍canvas常用的变形转换功能,共勉~

HTML5- Canvas入门(五)的更多相关文章
- HTML5 canvas入门
HTML5 Canvas入门 <canvas> 标签定义图形,比如图表和其他图像,您必须使用脚本来绘制图形.在画布上(Canvas)画一个红色矩形,渐变矩形,彩色矩形,和一些彩色的文字. ...
- html5 canvas 笔记五(合成与裁剪)
组合 Compositing globalCompositeOperation syntax: globalCompositeOperation = type 注意:下面所有例子中,蓝色方块是先绘制的 ...
- html5 Canvas绘制图形入门详解
html5,这个应该就不需要多作介绍了,只要是开发人员应该都不会陌生.html5是「新兴」的网页技术标准,目前,除IE8及其以下版本的IE浏览器之外,几乎所有主流浏览器(FireFox.Chrome. ...
- HTML5 canvas绘制线条曲线
HTML5 canvas入门 线条例子 1.简单线条 2.三角形 3.填充三角形背景颜色 4.线条颜色以及线条大小 5.二次贝塞尔曲线 6.三次贝塞尔曲线 <!doctype html> ...
- Canvas入门笔记-实现极简画笔
今天学习了Html5 Canvas入门,已经有大神写得很详细了http://www.cnblogs.com/tim-li/archive/2012/08/06/2580252.html#8 在学习过后 ...
- HTML5 Canvas 画图入门
HTML5 Canvas 画图入门 HTML5 Canvas 画图入门,仅供学习參考 <!DOCTYPE html> <html> <head> <meta ...
- HTML5简单入门系列(五)
前言 本篇将讲述HTML5的服务器发送事件(server-sent event) Server-Sent 事件 Server-Sent 事件是单向消息传递,指的是网页自动获取来自服务器的更新. 以前的 ...
- 06. Web大前端时代之:HTML5+CSS3入门系列~HTML5 画布
Web大前端时代之:HTML5+CSS3入门系列:http://www.cnblogs.com/dunitian/p/5121725.html 我们先看看画布的魅力: 初始画布 canvas默认是宽3 ...
- html5 基础入门
html5 基础入门 前言介绍 HTML5草案的前身名为 Web Applications 1.0,于2004年被WHATWG提出,于2007年被W3C接纳,并成立了新的 HTML工作团队. 如果从狭 ...
- 《html5 从入门到精通》读书笔记(一)
今天看了<html5 从入门到精通>这本书,感觉阅读下来很舒心,不像阅读其他书籍很揪心.html增加的知识点,我觉得非常有价值,看完几章记录了一些内容,不但能巩固,也为下次遗忘知识点做好准 ...
随机推荐
- async & await 的前世今生(Updated)
async 和 await 出现在C# 5.0之后,给并行编程带来了不少的方便,特别是当在MVC中的Action也变成async之后,有点开始什么都是async的味道了.但是这也给我们编程埋下了一些隐 ...
- BIOS中未启用虚拟化支持系列~~例如:因此无法安装Hyper-V
异常处理汇总-服务器系列:http://www.cnblogs.com/dunitian/p/4522983.html 一般都是启动一下CUP虚拟化就可以了 比如华硕的:
- 结巴分词3--基于汉字成词能力的HMM模型识别未登录词
作者:zhbzz2007 出处:http://www.cnblogs.com/zhbzz2007 欢迎转载,也请保留这段声明.谢谢! 1 算法简介 在 结巴分词2--基于前缀词典及动态规划实现分词 博 ...
- 使用python抓取婚恋网用户数据并用决策树生成自己择偶观
最近在看<机器学习实战>的时候萌生了一个想法,自己去网上爬一些数据按照书上的方法处理一下,不仅可以加深自己对书本的理解,顺便还可以在github拉拉人气.刚好在看决策树这一章,书里面的理论 ...
- 浅谈web攻防
CSRF 跨站请求伪造(Cross-Site Request Forgery) -原理- 从上图可以看出,要完成一次CSRF攻击,受害者必须依次完成两个步骤: 1.登录受信任网站A,并在本地生成Coo ...
- kafka配置与使用实例
kafka作为消息队列,在与netty.多线程配合使用时,可以达到高效的消息队列
- Windows 常用运行库下载 (DirectX、VC++、.Net Framework等)
经常听到有朋友抱怨他的电脑运行软件或者游戏时提示缺少什么 d3dx9_xx.dll 或 msvcp71.dll.msvcr71.dll又或者是 .Net Framework 初始化之类的错误而无法正常 ...
- MAVEN学习-第一个Maven项目的构建
MAVEN安装成功之后就可以进行项目的构建和管理了: 为什么要用maven进行项目的构建和管理? 对于初学者来说一个最直接的也是最容易里的优点在于JAR包的管理,相对于以前开发一个项目的时候我们需要用 ...
- C# Entity Framework并发处理
原网站:C# Entity Framework并发处理 在软件开发过程中,并发控制是确保及时纠正由并发操作导致的错误的一种机制.从 ADO.NET 到 LINQ to SQL 再到如今的 ADO.NE ...
- const let,console.log('a',a)跟console.log('a'+a)的区别
const 创建一个只读的常量 let块级作用域 const let重复赋值都会报错 console.log('a',a) a console.log('a'+a) a2 逗号的值会有空格:用加号的值 ...