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增加的知识点,我觉得非常有价值,看完几章记录了一些内容,不但能巩固,也为下次遗忘知识点做好准 ...
随机推荐
- Cmder--Windows下命令行利器
cmder cmder是一个增强型命令行工具,不仅可以使用windows下的所有命令,更爽的是可以使用linux的命令,shell命令. 安装包 安装包链接 下载后,直接解压即用. 修改命令提示符λ为 ...
- 菜鸟Python学习笔记第一天:关于一些函数库的使用
2017年1月3日 星期二 大一学习一门新的计算机语言真的很难,有时候连函数拼写出错查错都能查半天,没办法,谁让我英语太渣. 关于计算机语言的学习我想还是从C语言学习开始为好,Python有很多语言的 ...
- ASP.NET Aries 入门开发教程8:树型列表及自定义右键菜单
前言: 前面几篇重点都在讲普通列表的相关操作. 本篇主要讲树型列表的操作. 框架在设计时,已经把树型列表和普通列表全面统一了操作,用法几乎是一致的. 下面介绍一些差距化的内容: 1:树型列表绑定: v ...
- HTML5 localStorage本地存储
介绍 localStorage(本地存储)的使用方式.包括对存储对象的添加.修改.删除.事件触发等操作. 目录 1. 介绍 1.1 说明 1.2 特点 1.3 浏览器最小版本支持 1.4 适合场景 2 ...
- 【.net 深呼吸】跨应用程序域执行程序集
应用程序域,你在网上可以查到它的定义,凡是概念性的东西,大伙儿只需要会搜索就行,内容看了就罢,不用去记忆,更不用去背,“名词解释”是大学考试里面最无聊最没水平的题型. 简单地说,应用程序域让你可以在一 ...
- 通过重建Hosting系统理解HTTP请求在ASP.NET Core管道中的处理流程[下]:管道是如何构建起来的?
在<中篇>中,我们对管道的构成以及它对请求的处理流程进行了详细介绍,接下来我们需要了解的是这样一个管道是如何被构建起来的.总的来说,管道由一个服务器和一个HttpApplication构成 ...
- MyBatis基础入门--知识点总结
对原生态jdbc程序的问题总结 下面是一个传统的jdbc连接oracle数据库的标准代码: public static void main(String[] args) throws Exceptio ...
- BPM配置故事之案例1-配置简单流程
某天,Boss找到了信息部工程师小明. Boss:咱们新上了H3 BPM,你研究研究把现在的采购申请流程加上去吧,这是采购申请单. 小明:好嘞 采购申请单 小明回去后拿着表单想了想,开始着手配置. 他 ...
- mysql查询性能优化
mysql查询过程: 客户端发送查询请求. 服务器检查查询缓存,如果命中缓存,则返回结果,否则,继续执行. 服务器进行sql解析,预处理,再由优化器生成执行计划. Mysql调用存储引擎API执行优化 ...
- github中的watch、star、fork的作用
[转自:http://www.jianshu.com/p/6c366b53ea41] 在每个 github 项目的右上角,都有三个按钮,分别是 watch.star.fork,但是有些刚开始使用 gi ...