三人行必有我师焉,择其善者而从之,其不善者而改之
三人行必有我师焉,择其善者而从之,其不善者而改之
三人行必有我师焉,择其善者而从之,其不善者而改之
三人行必有我师焉,择其善者而从之,其不善者而改之
三人行必有我师焉,择其善者而从之,其不善者而改之

  canvas的像素级操作——4.关注性能 作者:Neoxone    发表时间: 2012年01月17号,星期二     阅读:19,155 次  

我们开篇就提过,canvas的像素级操作相对来说是很低效的。

我们试着写一个图片切割效果。

对于这个效果,因为我们并不需要操作图像的rgba数据,而只是把图像进行分割,所以利用putImageData的后四个可见区参数进行了设置就行了。
但是,这样做的性能却非常不理想。因为我们操作的ImageData数据实在太多了,循环执行了2750遍,相当于我们对整幅图像进行了2750次的像素级复制,而其实在可见区之外的ImageData数据并不是我们所需要的。

那么,我们在对源图getImageData的时候,可以只获取我们需要的ImageData。

第二种做法虽然在循环的时候多运行了一个方法(共执行2750次的getImageData和putImageData方法),但因为操作的ImageData少了2750倍,所以在效率上比第一种方式高了很多。

但从流程上来讲,我们只需要在刚开始的时候获取一次源图的ImageData(执行getImageData),对数据进行再排列后,最后再输出一次新的ImageData(执行putImageData)就可以了。
根本不需要在循环中反复调用getImageData和putImageData。所以现在的关键点是get和put之间的如何对数据进行重排列。

ImageData.data可以看做一个矩形矩阵,我们已知,它的序列号(n)与ImageData.width(w),及x轴序列号(x),y轴序列号(y)的关系是:n = ((y * w) + x) * 4; (其中的4表示了RGBA四个数据)。我们要的新的输出ImageData,其实是x加倍,y加倍,w加倍的一个新矩阵。那么新矩阵序号与原x,y,w的关系式应该是:t = ((y * 2 * w * 2) + x * 2) * 4;

第三种方法相对于第二种方法的效率提高了十几倍。第三种方法的关键点是找出新旧矩阵之间的关系,对于我们这一例来说还比较容易,复杂一点的,算法可就没这么简单了。

——————————————
重要补充:

我们先总结下三种方法:第一种:效率低差,但理解起来最简单。第三种,算法复杂,但效率最高。第二种,折中。
然而,如果使用的是webkit,opera浏览器,我们会发现第一种方法的效率比之第三种方法居然差不了多少!
可以看出webkit,opera对putImageData做过优化。对于putImageData(imgdata, x, y, x, y, 1 ,1)方法,webkit,opera只对(x,y,1,1)这个1平方px区域内的数据进行了put操作,区域外的数据并没有进行操作,这样在效率上会有很大的提高。
可惜的是firefox(9)就没有做过优化。要加油啊,Mozila!
最新测试了下ie9,结果显示第一种方法的效率的确很低,而第二种方法比第一种方法的效率还要低一倍。看来ie9果然也没用对putImageData进行优化,而且ie9下的getImageData也没用像其他浏览器下那么优化。对于getImageData(x,y,1,1)的获取,它操作的整个图像的像素数据的,而不是那个1平方px内的数据。

目前来说,考虑到各个浏览器原生方法的效率问题,第三种方法是最优的,即不要反复调用getImageData和putmageData,因为某些浏览器下一旦调用就是操作全部imageData的,而不会看你的参数。不过在未来,各个浏览器肯定会对原生方法进行优化的,在考虑第一种方法的时候就不用有所顾忌了!

   

  canvas的像素级操作——3.使用卷积矩阵 作者:Neoxone    发表时间: 2012年01月12号,星期四     阅读:7,178 次  

利用卷积矩阵(Convolution Matrix)操作像素,我们可以很方便的得到诸如模糊、边缘检测、锐化、浮雕和斜角这样的效果。

常用的矩阵类型是 3 x 3 矩阵,另外还有5 x 5的矩阵。

工作原理:http://flex4jiaocheng.com/blog/280

点阵图中的每一个像素被称为“初步像素”,用与卷积矩阵同样面积的“初步像素”从左到右从上到下与卷积矩阵中相应位置的值相乘,再将得到的9个或25个中间值相加,就得到了“初步像素”矩阵中央的一个值的结果值再与Divisor(因子)相除,与Offset(偏移量)相加,最后得到终值。如下图所示:

应用卷积矩阵实现特效:

上面demo中卷积的实现函数来自于在HTML 5 的 Canvas 中应用卷积矩阵对图像处理

推荐一篇有趣的文章:卷积的物理意义

   

  canvas的像素级操作——1.引子 作者:Neoxone    发表时间: 2012年01月6号,星期五     阅读:10,592 次  

本文是对《MDC的canvas经典教程辑和个人学习笔记》的补遗,也是canvas像素级操作系列文章的一个引子。
后面我会陆续发一些canvas素级操作的应用,特效方面的文章。

既然是引子,那就不能开门见山的介绍了,我们先讲讲如何复制canvas.
已知一个image对象,我们将其绘制进canvas的方法是什么?drawImage。(当然使用createPattern模板填充也是一个方法)。
那已知一个canvas对象,我们将其绘制进另一个canvas的方法呢?
答案还是drawImage,drawImage算是一个很辽阔的方法了,不仅可以绘image,也可以绘canvas对象,甚至还可以绘video的帧。

并且他拥有大量参数:(Image [, vXSrc] [, vYSrc] [, vWSrc] [, vHSrc], vXDest, vYDest [, vWDest] [, vHDest]),这个读者可以先不管,往下看。

那么,除了drawImage这个方法,还有没有其他方法呢——有,putImageData方法隆重登场。

上面的cloneData方法就是通过将源canvas中像素数据ImageData,输出(putImageData)到新的canvas中,达到复制作用。

不过,我们在获取和输出ImageData的过程中,并没有对ImageData做过任何处理,而这个ImageData数据是包含{width,height,CanvasPixelArray},其中CanvasPixelArray包含了图像(canvas也可看做图像)的每一个像素的RGBA数据,可见中间的操作空间是很大的,以后我们会做重点讨论。

插注:CanvasPixelArray——在最新标准中已引入一个Uint8ClampedArrayTyped Array来替代 ,在各浏览器实现之后,将使得对ImageData的操作更快速更便捷。参考例子:http://hacks.mozilla.org/2011/12/faster-canvas-pixel-manipulation-with-typed-arrays/

下面介绍一下像素操作的三个方法:

createImageData();
getImageData();
putImageData();

createImageData的参数是(w,h)可以新建一个W*H尺寸的新的ImageData【firefox3.5开始支持】,不过也可以使用参数(anotherImageData)来创建【firefox5开始支持】。

getImageData的参数(x,y,w,h)表示起点x,y,尺寸w,h。可以获取canvas.context中在参数范围内的ImageData。

putImageData的参数使用要着重要介绍下(和drawImage的参数可以触类旁通):

putImageData参数有(ImageData, dx, dy [, DirtyX] [, DirtyX] [, DirtyWidth] [, DirtyHeight])

imageData:包含了图像的width,height,还有一个CanvasPixelArray,前面已经提了。
dx, dy:表示绘图起始位置。相对于canvas区域左上角。
后面四个可选参数:表示可见区范围。相对于起绘点,即上面的参数dx,dy表示的点。缺省为0,0,ImageData.width,ImageData.height。

   

  获取元素尺寸和位置的两个冷门方法 作者:Neoxone    发表时间: 2012年01月4号,星期三     阅读:3,493 次  

获取元素的尺寸,似乎offsetWidth/Height就可以了,还需要什么方法吗?
的确,一般情况下,获取元素尺寸,用offsetWidth/Height就搞定了,但如果这个元素是display:none的呢?
试验一下就知道,none元素的尺寸是0。

所以需要我们下面的这个方法来获取。

第二个,获取元素的位置。
或许你经常使用offsetLeft/Top来获取位置,不过offsetLeft/Top是相对 offsetParent的位置(在ie6,7下是相对直接父级的),并且在firefox下还有些小bug

下面这个方法提供获取元素相对于窗口(页面可视区)的距离。

这个方法是由ie提出的,不过在其他浏览器吸收之后,还加了width,height两个值。

重复一下,这个方法是相对于页面窗口的,至于相对于整个页面文档的距离,那只需加上scrollTop这些值就行了,不做赘述。

   

  mouseenter/mouseleave事件和delegate方法的实现 作者:Neoxone    发表时间: 2011年12月18号,星期天     阅读:17,166 次  

众所周知,事件onmouseover和onmouseout有一个极其不好的问题,就是在绑定元素内部的子元素上滑动会反复触发事件,及执行绑定的方法。

而ie在很早的时候有提供了另一对事件:mouseenter和mouseleaver。顾名思义,就是只有当mouse滑进滑出绑定元素的时候,才会触发。

但是,这本来只是ie的私有属性,虽然已属于DOM3 Event草案当中,其他浏览器的支持率并不是很高,目前看来,Opera11.10已提供支持,而Firefox到10.0会提供支持,Webkit的暂无消息。
所以,如果想要用,我们得自己动手。

先搞一个通用的事件绑定函数:

  1. var addEvent = function( target,type,fn ) {
  2.     if(target.addEventListener)
  3.     {
  4.         target.addEventListener(type,fn,false);
  5.     }
  6.     else if(target.attachEvent)
  7.     {
  8.         target.attachEvent("on" + type,fn);
  9.     }
  10. };
  11. var removeEvent = function(target,type,fn ) {
  12.     if(target.addEventListener)
  13.     {
  14.         target.removeEventListener(type,fn,false);
  15.     }
  16.     else if(target.attachEvent)
  17.     {
  18.         target.detachEvent("on" + type,fn);
  19.     }
  20. };

我们的mouseenter/leave是通过mouseover/out来实现的,只需屏蔽mouseover/out在元素内部触发时的事件传播即可。

为了解除绑定,我们设计了一个events._mouseFn来保存绑定的方法,在解除操作时读取对应的方法进行解绑。因为事件可以绑定多个方法,我们需要保存对应的方法,以便之后对应解除。
当然如果这里如果按面向对象的思路实现,就可以各自保存,而不需要保持在同一个events._mouseFn对象下。但每绑定一个事件,都需实例化一个对象,显得很多余,所以不采用面向对象的模式。

接下来是文章的第二部分,我们来实现下jquery中提供的delegate方法。
(delegate是live方法的扩展版。delegate是基于live的,live是基于bind的。在jquery1.7中又被封装进了on方法。1.7中的on方法是一个很辽阔的方法。其实封装的越厉害,效率就越差了,这也是为什么我们选择自己做简单封装的原因,而不是使用jquery已封装好的)。
这个方法可以将想要绑定在子级元素上的事件方法,委托绑定在其父级上,用事件传播机制来触发执行。
这么做的好处有:
1:如果子级有n个并列元素需要绑定,绑子级需要绑n次,而将其绑定在父级上则只需绑定一次,这是很高效的。
2:如果子级元素有动态增加的话,新增元素是没有绑定过任何事件方法的。而如果之前选择的是绑定其父级,就不会有这个问题。

这里的实现思路是这样的:如果触发事件的元素,是你想要绑定的元素的子级(当然他肯定已是委托实际绑定元素的子级),就执行绑定的事件方法,否则方法就不执行,看上去就像方法没绑定过一样。
同实现onmouseenter一样,我们也设计了一个events._deleFn来用于后面的解绑方法undelegate的实现。
另外针对ie,我们还解决了两个问题:
1. 绑定方法内的this指向问题。我们用.apply执行绑定方法来解决。
2. 使用apply调用绑定方法,就必须考虑如何解绑方法。原理和_mouseFn还有_deleFn一样。

在使用delegate时,我们同样遇到了mouseover/out的问题。
我们的解决方案是:不罗嗦,直接将mouseover/out处理成mouseenter/leave

最后,整理一下,封装一个支持mouseenter 和 mouseleave事件,delegate方法 及其他们的解除绑定的方法的 功能函数库。

  1. window.events = {}
  2. events._deleFn = {}; //保存delegate所绑定的方法   
  3. events._mouseFn ={}; //保存“onmouseenter”和“onmouseleave”所绑定的方法
  4. events._ieFunc = {}; //由于保存在ie下绑定的方法
  5.  
  6. events._mouseHandle = function(fn){
  7.     /* 实现mouseenter/leave 的转换方法,符合条件时才会执行 */
  8.     var func = function(event){
  9.         var target = event.target;
  10.         var parent = event.relatedTarget; //在onmouseover/out操作中,相关的另一个节点
  11.         while( parent && parent != this ){ 
  12.             try{ parent = parent.parentNode; }
  13.             catch(e){break;}
  14.         }
  15.         /* 只有当相关节点的父级不会是绑定的节点时(即二者不是父子的包含关系),才调用fn,否则不做处理 */
  16.         ( parent != this ) && (fn.call(target,event));
  17.     };
  18.     return func;
  19. }
  20.  
  21. events._delegateHandle = function(obj,selector,fn){
  22.     /* 实现delegate 的转换方法,符合条件时才会执行 */
  23.     var func = function(event){
  24.         var event = event || window.event;
  25.         var target = event.srcElement || event.target;
  26.         var parent = target;
  27.         function contain(item,elmName){
  28.             if(elmName.split('#')[1]){ //by id
  29.                 if(item.id && item.id === elmName.split('#')[1]) return true;
  30.             } 
  31.             if(elmName.split('.')[1]){ //by class
  32.                 if(hasClass(item, elmName.split('.')[1])) return true;
  33.             }
  34.             if(item.tagName == elmName.toUpperCase())  return true; //by tagname
  35.             return false;
  36.         }
  37.  
  38.         while(parent){
  39.             /* 如果触发的元素,属于(selector)元素的子级。 */
  40.             if(obj == parent) return false; //触发元素是自己
  41.             if(contain(parent,selector)){
  42.                 if(event.type == 'mouseover' || event.type == 'mouseout'){
  43.                     /*
  44.                     * 将mouseover/out直接处理成mouseenter/leave: 事件相关元素不属于绑定元素的子级,才绑定方法
  45.                     */
  46.                     //事件相关元素。ie下使用toElement和fromElement,其他用relatedTarget。
  47.                     var related = event.relatedTarget || ((event.type == 'mouseout') ? event.toElement : event.fromElement);
  48.                     if(contain(target,selector) || contain(related,selector)) {
  49.                         /* 如果,触发元素或相关元素属于绑定元素(selector)。执行方法 */
  50.                         fn.call(obj,event);
  51.                         return;
  52.                     }
  53.                     while( related && !contain(related,selector)){ 
  54.                           related = related.parentNode;
  55.                     }
  56.                     /* 事件相关元素,不属于绑定元素(selector)的子级,执行方法  */
  57.                     !contain(related,selector) && (fn.call(obj,event));
  58.                 }else{
  59.                     fn.call(obj,event);
  60.                 }
  61.                 return;
  62.             }
  63.             parent = parent.parentNode;   
  64.         }
  65.     };
  66.     return func;
  67. };
  68.  
  69. events.addEvent = function(target,type,fn){
  70.     if (!target) return false;
  71.     var add = function(obj){
  72.         if(obj.addEventListener){   
  73.             if(obj.onmouseenter !== undefined){
  74.                 //for opera11,firefox10。他们也支持“onmouseenter”和“onmouseleave”,可以直接绑定
  75.                 obj.addEventListener(type,fn,false)
  76.                 return ;
  77.             }
  78.             if(type=="mouseenter" || type=="mouseleave" ){ 
  79.                 var eType = (type=="mouseenter") ? "mouseover" : "mouseout";
  80.                 var fnNew = events._mouseHandle(fn);
  81.                 obj.addEventListener(eType,fnNew,false);
  82.                  /* 将方法存入events._mouseFn,以便以后remove */
  83.                 if(!events._mouseFn[obj]) events._mouseFn[obj] = {};
  84.                 if(!events._mouseFn[obj][eType]) events._mouseFn[obj][eType] = {};
  85.                     events._mouseFn[obj][eType][fn] = fnNew;
  86.             }else{
  87.                 obj.addEventListener(type,fn,false);
  88.             }
  89.         }else{
  90.             // for ie
  91.             if(!events._ieFunc[obj]) events._ieFunc[obj] = {};
  92.             if(!events._ieFunc[obj][type]) events._ieFunc[obj][type] = {};
  93.             events._ieFunc[obj][type][fn] = function(){
  94.                 fn.apply(obj,arguments);
  95.             };
  96.             obj.attachEvent("on" + type,events._ieFunc[obj][type][fn]);
  97.         }
  98.     }
  99.     if(isDOMs(target)) {
  100.         for(var i=0, l = target.length; i < l; i++){
  101.             add(target[i])
  102.         }
  103.     }else{
  104.         add(target);
  105.     }
  106. };
  107.  
  108. events.removeEvent = function(target,type,fn) {
  109.     if (!target) return false;
  110.     var remove = function(obj){
  111.         if(obj.addEventListener){   
  112.             if(obj.onmouseenter !== undefined){
  113.                 obj.removeEventListener(type,fn,false)
  114.                 return ;
  115.             }
  116.             if(type=="mouseenter" || type=="mouseleave" ){ 
  117.                 var eType = (type=="mouseenter") ? "mouseover" : "mouseout";
  118.                 if(!events._mouseFn[obj][eType][fn]) return;
  119.                 obj.removeEventListener(eType,events._mouseFn[obj][eType][fn],false);
  120.                 events._mouseFn[obj][eType][fn]={};
  121.             }else{
  122.                 obj.removeEventListener(type,fn,false);
  123.             }
  124.         }else{
  125.             //for ie
  126.             if(!events._ieFunc[obj] ||!events._ieFunc[obj][type] || !events._ieFunc[obj][type][fn]) return;
  127.             obj.detachEvent("on" + type, events._ieFunc[obj][type][fn],false);
  128.             events._ieFunc[obj][type][fn]={};
  129.         }
  130.     }
  131.     if(isDOMs(target)) {
  132.         for(var i=0, l = target.length; i < l; i++){
  133.             remove(target[i])
  134.         }
  135.     }else{
  136.         remove(target);
  137.     }
  138. };
  139.  
  140. events.delegate = function(obj,selector,type,fn){
  141.     if (!obj || !selector) return false;
  142.     var fnNew = events._delegateHandle(obj,selector,fn);
  143.     events.addEvent(obj,type,fnNew);
  144.     /* 将绑定的方法存入events._deleFn,以便之后解绑操作 */
  145.     if(!events._deleFn[selector]) events._deleFn[selector] = {};
  146.     if(!events._deleFn[selector][type]) events._deleFn[selector][type] = {};
  147.     events._deleFn[selector][type][fn] = fnNew;
  148. };
  149.  
  150. events.undelegate = function(obj,selector,type,fn){
  151.     if (!obj || !selector || !events._deleFn[selector]) return false;
  152.     var fnNew = events._deleFn[selector][type][fn];
  153.     if(!fnNew) return;
  154.     events.removeEvent(obj,type,fnNew);
  155.     events._deleFn[selector][type][fn] = null;
  156. };