美文网首页
基于canvas实现的马赛克画笔功能

基于canvas实现的马赛克画笔功能

作者: YomonAh | 来源:发表于2017-10-21 21:56 被阅读0次
    masic.gif

    展示demo用一张我家孝敏美美的画报>w<~
    介绍下这款基于js和canvas实现的前端马赛克画笔,每次执行步骤都可以撤销,可反复绘制
    实现原理分以下几个步骤:

    1. 将图片导入到canvas中:
    var imgObj = new Image();
    imgObj.crossOrigin = "Anonymous"; //用于解决图片跨域问题,但在chrome下依旧无效,建议起个node服务或者用safari打开
    imgObj.src = 'timg.jpeg';
    //待图片加载完后,将其显示在canvas上
    imgObj.onload = function(){
      context.drawImage(this, 0, 0,canvas.width,canvas.height);//this即是imgObj,保持图片的原始大小
       /**
      步骤二
      */
    }
    

    这里注意下关于图片跨域的报错,可以在网上找些解决方案:http://www.cnblogs.com/haimingpro/p/6098204.html

    1. 开始处理画笔事件:触发-移动-提笔
    var quan = 3; //马赛克的大小
    var num = 9; //一次操作包含马赛克的个数
    
    canvas.onmousedown = function(ev){
      var ev=ev || window.event;
      var dx = ev.clientX-canvas.offsetLeft;
      var dy = ev.clientY-canvas.offsetTop;
      drawLine(obj,dx,dy); //开始画马塞克
      document.onmousemove = function(ev){
      var ev = ev || window.event;
      var mx = ev.clientX-canvas.offsetLeft;
      var my = ev.clientY-canvas.offsetTop;
      //当拖拽的距离超过马赛克的直径再画下一个马赛克
      if(Math.pow(dx-mx,2)+Math.pow(dy-my,2)>= Math.pow(quan*num,2)){  //
          drawLine(obj,mx,my);
            dx = mx;
            dy = my;
        }
      };
      document.onmouseup = function(){
        document.onmousemove = null;
        document.onmouseup = null;
      };
    }
    

    马赛克大小和数量可自定义,马赛克大小越大,那么图像模糊化会越重,数值越小,则越接近原图;两者的数值直接影响画笔的大小。

    1. 生成马赛克
    //原始图像
    var originalImgData = context.getImageData(0,0,canvas.width,canvas.height);  
    var originalPxData = originalImgData.data;  
                  
    //用于循环修改  
    var modifyImgData = context.getImageData(0,0,canvas.width,canvas.height);  
    var modifyPxData = modifyImgData.data;  
    
    for(var i=dx-quan*num;i<dx+quan*num;i = i+2*quan+1){  
      for(var j=dy-quan*num;j<dy+quan*num;j = j+2*quan+1){
         //中心点(dx,dy)
         //注意!这里的if判断是为了把画笔处理成圆的,有两种方案,下面细述,如果把if判断去掉,画笔默认是正方形的
         // if(Math.pow(i-dx,2)+Math.pow(j-dy,2) <= Math.pow(quan*num/2,2)){
         if(!((i==dx-quan*num&&j==dy-quan*num)||(i==dx-quan*num&&j==dy-quan*num+2*quan+1)||
            (i==dx-quan*num&&j==dy-quan*num+4*quan+2)||(i==dx-quan*num&&j==dy-quan*num+12*quan+6)||
            (i==dx-quan*num&&j==dy-quan*num+14*quan+7)||(i==dx-quan*num&&j==dy-quan*num+16*quan+8)||
            (i==dx-quan*num+16*quan+8&&j==dy-quan*num)||(i==dx-quan*num+16*quan+8&&j==dy-quan*num+2*quan+1)||
            (i==dx-quan*num+16*quan+8&&j==dy-quan*num+4*quan+2)||(i==dx-quan*num+16*quan+8&&j==dy-quan*num+12*quan+6)||
            (i==dx-quan*num+16*quan+8&&j==dy-quan*num+14*quan+7)||(i==dx-quan*num+16*quan+8&&j==dy-quan*num+16*quan+8)||
            (i==dx-quan*num+2*quan+1&&j==dy-quan*num)||(i==dx-quan*num+4*quan+2&&j==dy-quan*num)||
            (i==dx-quan*num+12*quan+6&&j==dy-quan*num)||(i==dx-quan*num+14*quan+7&&j==dy-quan*num)||
            (i==dx-quan*num+2*quan+1&&j==dy-quan*num+16*quan+8)||(i==dx-quan*num+4*quan+2&&j==dy-quan*num+16*quan+8)||
            (i==dx-quan*num+12*quan+6&&j==dy-quan*num+16*quan+8)||(i==dx-quan*num+14*quan+7&&j==dy-quan*num+16*quan+8))){
               var sumR = 0;  
               var sumG = 0;  
               var sumB = 0;  
               //找他周围的元素 
               for(var x = -quan;x<=quan;x++){  
                  for(var y = -quan;y<=quan;y++){  
                     var xx = i+x;  
                     var yy = j+y;  
                     var pp = yy*canvas.width+xx; //周围的元素。  
                     sumR += originalPxData[pp*4+0];  
                     sumG += originalPxData[pp*4+1];  
                     sumB += originalPxData[pp*4+2];  
                   }  
                 }  
                      
                 var totlal = (2*quan+1)*(2*quan+1);  
                 var avgR = sumR/totlal;  
                 var avgG = sumG/totlal;  
                 var avgB = sumB/totlal;  
                      
                 for(var x = -quan;x<=quan;x++){  
                    for(var y = -quan;y<=quan;y++){  
                      var xx = i+x;  
                      var yy = j+y;  
                      var pp = yy*canvas.width+xx; //周围的元素。  
                      modifyPxData[pp*4+0] = avgR;  
                      modifyPxData[pp*4+1] = avgG;  
                      modifyPxData[pp*4+2] = avgB;  
                    }  
                 }  
               }  
             }
          } 
         context.putImageData(modifyImgData,0,0,0,0,canvas.width,canvas.height); 
    

    原理是通过循环找到马赛克区域的每个像素点中心的颜色,将其覆盖到整块像素区域,简单画个草图示意下,马赛克画笔区域默认是正方形:


    草图1.jpeg

    接下来我们将画笔处理为圆形:
    有两种方案:1. 将距离超过马赛克画笔区域半径的像素点过滤掉,如图:


    草图2.jpeg
    1. 直接过滤掉不需要的点,使马赛克画笔接近成圆形


      草图3.jpeg

      一共要过滤20个点,比较繁琐,但测试了下效果,感觉还是方案二比较好
      到这里,我们实现马赛克画笔功能的流程就走完了

    2. 最后说下撤销功能
      我们只需要在每次绘画之前,先把图像做个保存,存入到缓存数组中,撤销的时候,根据堆栈原理,一步步还原:

    //修改缓存
    var lastImgArr = [];
    
    canvas.onmousedown = function(ev){
      //每次下笔前先保存
      lastImgArr.push(context.getImageData(0,0,canvas.width,canvas.height));
    }
    //撤销修改
    document.getElementById('revoked').onclick = function(){
      if(lastImgArr && lastImgArr.length){
        context.putImageData(lastImgArr.pop(), 0, 0);
       }
    }
    

    查看完整源码:[github-link]:https://github.com/yomonah/mosaic-js
    感谢阅读。

    相关文章

      网友评论

          本文标题:基于canvas实现的马赛克画笔功能

          本文链接:https://www.haomeiwen.com/subject/uuewuxtx.html