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

⑵ 我们也可以通过添加 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。

说到裁剪我们顺便说说另一个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还专门提供了一个实例(点我查看),查看该页面源码即可获得全部代码,有兴趣的朋友可以研究下。

本章就讲到这里,下一章应该是开始将变形和动画了,共勉~

时间: 2024-12-08 16:34:20

HTML5- Canvas入门(五)的相关文章

HTML5 Canvas入门

HTML5的canvas(画布)元素使用JavaScript在网页上绘制图像.下面以一个简单例子及其效果图(图1)开始: <!DOCTYPE HTML> <html> <head> <style type="text/css"> canvas{border:dashed 2px #CCC} </style> <script type="text/javascript"> /* canvas元素本

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

组合 Compositing globalCompositeOperation syntax: globalCompositeOperation = type 注意:下面所有例子中,蓝色方块是先绘制的,即“已有的 canvas 内容”,红色圆形是后面绘制,即“新图形”. source-over 这是默认设置,新图形会覆盖在原有内容之上. destination-over 会在原有内容之下绘制新图形. source-in 新图形会仅仅出现与原有内容重叠的部分.其它区域都变成透明的. destina

html5 Canvas绘制图形入门详解

html5,这个应该就不需要多作介绍了,只要是开发人员应该都不会陌生.html5是「新兴」的网页技术标准,目前,除IE8及其以下版本的IE浏览器之外,几乎所有主流浏览器(FireFox.Chrome.Opera.Safari.IE9+)都已经开始支持html5了.除此之外,在移动浏览器市场上,众多的移动浏览器也纷纷展开关于「html5的支持能力以及性能表现」的军备竞赛.html作为革命性的网页技术标准,再加上众多浏览器厂商或组织的鼎力支持,可以想见,html5将会成为未来网页技术的领头羊. ht

HTML5 canvas绘制线条曲线

HTML5 canvas入门 线条例子 1.简单线条 2.三角形 3.填充三角形背景颜色 4.线条颜色以及线条大小 5.二次贝塞尔曲线 6.三次贝塞尔曲线 <!doctype html> <html> <head> <meta charset="utf-8"/> <meta name="keywords" content="脚本小子_小贝_HTML5_canvas线条"/> <me

HTML5 Canvas 绘图入门

HTML5 Canvas 绘图入门 HTML5 Canvas 绘图入门,仅供学习参考 <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>HTML5 移动Web开发指南</title> <style type="text/css"> h1, h5 { text-align: center; } canvas {

HTML5简单入门系列(五)

前言 本篇将讲述HTML5的服务器发送事件(server-sent event) Server-Sent 事件 Server-Sent 事件是单向消息传递,指的是网页自动获取来自服务器的更新. 以前的做法是网页不断的询问(向服务器发送请求)是否有可用的更新.通过服务器反馈之后,获得更新. 轮训方案 我们使用上篇HTML5简单入门系列(四)web worker的技术简单实现一下该轮训方案,主动向服务器询问是否有更新. 由于web worker不能访问document等对象,是不能和jQuery连用

HTML5 canvas 绘制精美的图形

HTML5 是一个新兴标准,它正在以越来越快的速度替代久经考验的 HTML4.HTML5 是一个 W3C “工作草案” — 意味着它仍然处于开发阶段 — 它包含丰富的元素和属性,它们都支持现行的 HTML 4.01 版本规范.它还引入了几个新元素和属性,它们适用许多使用 web 页面的领域 — 音频.视频.图形.数据存储.内容呈现,等等.本文主要关注图形方面的增强:canvas. 新的 HTML5 canvas 是一个原生 HTML 绘图簿,用于 JavaScript 代码,不使用第三方工具.跨

赠书:HTML5 Canvas 2d 编程必读的两本经典

赠书:HTML5 Canvas 2d 编程必读的两本经典 这两年多一直在和HTML5 Canvas 打交道,也带领团队开发了世界首款基于HTML5 Canvas 的演示文档工具---AxeSlide(斧子演示,www.axeslide.com).在这个领域也积累了一些 经验,希望有机会和大家分享.今天是要给大家推荐两本这方面的书,同时会送一本书给大家. 要介绍的第一本书是我学习Canvas开发的入门书——<HTML5 Canvas核心技术:图形.动画与游戏开发>. 此书作者David Gear

HTML5新手入门指南

HTML5的发展越来越迈向成熟,很多的应用已经逐渐出现在你我日常生活中了,不只让传统网站上的互动Flash逐渐的被HTML5的技术取代,更重要的是可以透过HTML5的技术来开发跨平台的手机软件,让许多开发者感到十分兴奋! 当你开始想要学习.试图想要投入相关的开发时,由于HTML5的技术还在持续发展.进化当中,学习的资源也都比较零散,较难有一个整体的方向.在本篇文章中,笔者将会介绍HTML5的主要技术组成,并且提供一些学习资源让大家参考. HTML5到底是什么? 一般广义而言的HTML5则包含了H

使用html5 canvas绘制圆形或弧线

注意:本文属于<html5 Canvas绘制图形入门详解>系列文章中的一部分.如果你是html5初学者,仅仅阅读本文,可能无法较深入的理解canvas,甚至无法顺畅地通读本文.请点击上述链接以了解使用html5 canvas绘制图形的完整内容. 在html5中,CanvasRenderingContext2D对象也提供了专门用于绘制圆形或弧线的方法,请参考以下属性和方法介绍: arc(x, y, radius, startRad, endRad, anticlockwise) 在canvas画