今天要介绍的是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之后才执行绘图代码,防止代码在图片加载到之前就执行。效果如下:

⑵ 我们也可以通过添加 widthheight 参数来缩放图片:

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对象都有其 widthheight 属性,对应其宽度和高度:

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入门(五)的更多相关文章

  1. HTML5 canvas入门

    HTML5 Canvas入门 <canvas> 标签定义图形,比如图表和其他图像,您必须使用脚本来绘制图形.在画布上(Canvas)画一个红色矩形,渐变矩形,彩色矩形,和一些彩色的文字. ...

  2. html5 canvas 笔记五(合成与裁剪)

    组合 Compositing globalCompositeOperation syntax: globalCompositeOperation = type 注意:下面所有例子中,蓝色方块是先绘制的 ...

  3. html5 Canvas绘制图形入门详解

    html5,这个应该就不需要多作介绍了,只要是开发人员应该都不会陌生.html5是「新兴」的网页技术标准,目前,除IE8及其以下版本的IE浏览器之外,几乎所有主流浏览器(FireFox.Chrome. ...

  4. HTML5 canvas绘制线条曲线

    HTML5 canvas入门 线条例子 1.简单线条 2.三角形 3.填充三角形背景颜色 4.线条颜色以及线条大小 5.二次贝塞尔曲线 6.三次贝塞尔曲线 <!doctype html> ...

  5. Canvas入门笔记-实现极简画笔

    今天学习了Html5 Canvas入门,已经有大神写得很详细了http://www.cnblogs.com/tim-li/archive/2012/08/06/2580252.html#8 在学习过后 ...

  6. HTML5 Canvas 画图入门

    HTML5 Canvas 画图入门 HTML5 Canvas 画图入门,仅供学习參考 <!DOCTYPE html> <html> <head> <meta ...

  7. HTML5简单入门系列(五)

    前言 本篇将讲述HTML5的服务器发送事件(server-sent event) Server-Sent 事件 Server-Sent 事件是单向消息传递,指的是网页自动获取来自服务器的更新. 以前的 ...

  8. 06. Web大前端时代之:HTML5+CSS3入门系列~HTML5 画布

    Web大前端时代之:HTML5+CSS3入门系列:http://www.cnblogs.com/dunitian/p/5121725.html 我们先看看画布的魅力: 初始画布 canvas默认是宽3 ...

  9. html5 基础入门

    html5 基础入门 前言介绍 HTML5草案的前身名为 Web Applications 1.0,于2004年被WHATWG提出,于2007年被W3C接纳,并成立了新的 HTML工作团队. 如果从狭 ...

  10. 《html5 从入门到精通》读书笔记(一)

    今天看了<html5 从入门到精通>这本书,感觉阅读下来很舒心,不像阅读其他书籍很揪心.html增加的知识点,我觉得非常有价值,看完几章记录了一些内容,不但能巩固,也为下次遗忘知识点做好准 ...

随机推荐

  1. 加深一下BlockingQueue的认识

    认识BlockingQueue BlockingQueue是一种可以阻塞线程的队列,java中对这种队列提供了方法抽象,BlockingQueue则是抽象的接口. add:添加元素到队列里,添加成功返 ...

  2. 实时的.NET程序错误监控产品Exceptionless

    Exceptionless可以对ASP.NET, Web API, WebForms, WPF, Console, 和 MVC 应用提供错误监控.上传.报表服务.使用时需要在Exceptionless ...

  3. Zabbix基本配置及监控主机

    监控主机一版需要在被监控的主机上安装Zabbix Agent 监控主机 安装zabbix-agent 首先需要在被监控的主机上安装agent,可以下载预编译好的RPM进行安装,下载地址:http:// ...

  4. ASP.NET MVC5+EF6+EasyUI 后台管理系统 (源码购买说明)

    系列目录 升级日志 !!!重大版本更新:于2016-12-20日完成了系统的结构重构并合并简化了T4(这是一次重要的更新,不需要修改现有功能的代码),代码总行数比上个版本又少了1/3.更新了代码生成器 ...

  5. JQuery easyUI DataGrid 创建复杂列表头(译)

    » Create column groups in DataGrid The easyui DataGrid has ability to group columns, as the followin ...

  6. 【转】39个让你受益的HTML5教程

    闲话少说,本文作者为大家收集了网上学习HTML5的资源,期望它们可以帮助大家更好地学习HTML5. 好人啊! 不过,作者原来说的40个只有39个,因为第5个和第8个是重复的. 原文在此! 1. 五分钟 ...

  7. Spring中Bean的实例化

                                    Spring中Bean的实例化 在介绍Bean的三种实例化的方式之前,我们首先需要介绍一下什么是Bean,以及Bean的配置方式. 如果 ...

  8. 从阿里巴巴笔试题看Java加载顺序

    一.阿里巴巴笔试题: public class T implements Cloneable { public static int k = 0; public static T t1 = new T ...

  9. iOS开发 判断当前APP版本和升级

    从iOS8系统开始,用户可以在设置里面设置在WiFi环境下,自动更新安装的App.此功能大大方便了用户,但是一些用户没有开启此项功能,因此还是需要在程序里面提示用户的 方法一:在服务器接口约定对应的数 ...

  10. Mysql - 函数

    Mysql提供的函数是在是太多了, 很多我都见过, 别说用了. 园子里面, 有人弄了一个比较全的. MYSQL函数 我这里会将他写的完全拷贝下来, 中间会插入一些自己项目中使用过的心得 一.数学函数 ...