基于canvas實(shí)現(xiàn)的馬賽克畫筆功能

masic.gif

展示demo用一張我家孝敏美美的畫報(bào)>w<~
介紹下這款基于js和canvas實(shí)現(xiàn)的前端馬賽克畫筆,每次執(zhí)行步驟都可以撤銷,可反復(fù)繪制
實(shí)現(xiàn)原理分以下幾個(gè)步驟:

  1. 將圖片導(dǎo)入到canvas中:
var imgObj = new Image();
imgObj.crossOrigin = "Anonymous"; //用于解決圖片跨域問題,但在chrome下依舊無效,建議起個(gè)node服務(wù)或者用safari打開
imgObj.src = 'timg.jpeg';
//待圖片加載完后,將其顯示在canvas上
imgObj.onload = function(){
  context.drawImage(this, 0, 0,canvas.width,canvas.height);//this即是imgObj,保持圖片的原始大小
   /**
  步驟二
  */
}

這里注意下關(guān)于圖片跨域的報(bào)錯(cuò),可以在網(wǎng)上找些解決方案:http://www.cnblogs.com/haimingpro/p/6098204.html

  1. 開始處理畫筆事件:觸發(fā)-移動-提筆
var quan = 3; //馬賽克的大小
var num = 9; //一次操作包含馬賽克的個(gè)數(shù)

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;
  //當(dāng)拖拽的距離超過馬賽克的直徑再畫下一個(gè)馬賽克
  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;
  };
}

馬賽克大小和數(shù)量可自定義,馬賽克大小越大,那么圖像模糊化會越重,數(shù)值越小,則越接近原圖;兩者的數(shù)值直接影響畫筆的大小。

  1. 生成馬賽克
//原始圖像
var originalImgData = context.getImageData(0,0,canvas.width,canvas.height);  
var originalPxData = originalImgData.data;  
              
//用于循環(huán)修改  
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){
     //中心點(diǎn)(dx,dy)
     //注意!這里的if判斷是為了把畫筆處理成圓的,有兩種方案,下面細(xì)述,如果把if判斷去掉,畫筆默認(rèn)是正方形的
     // 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); 

原理是通過循環(huán)找到馬賽克區(qū)域的每個(gè)像素點(diǎn)中心的顏色,將其覆蓋到整塊像素區(qū)域,簡單畫個(gè)草圖示意下,馬賽克畫筆區(qū)域默認(rèn)是正方形:


草圖1.jpeg

接下來我們將畫筆處理為圓形:
有兩種方案:1. 將距離超過馬賽克畫筆區(qū)域半徑的像素點(diǎn)過濾掉,如圖:


草圖2.jpeg
  1. 直接過濾掉不需要的點(diǎn),使馬賽克畫筆接近成圓形


    草圖3.jpeg

    一共要過濾20個(gè)點(diǎn),比較繁瑣,但測試了下效果,感覺還是方案二比較好
    到這里,我們實(shí)現(xiàn)馬賽克畫筆功能的流程就走完了

  2. 最后說下撤銷功能
    我們只需要在每次繪畫之前,先把圖像做個(gè)保存,存入到緩存數(shù)組中,撤銷的時(shí)候,根據(jù)堆棧原理,一步步還原:

//修改緩存
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
感謝閱讀。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,039評論 25 709
  • 系列文章之 Android中自定義View(一)系列文章之 Android中自定義View(二)系列文章之 And...
    YoungerDev閱讀 2,333評論 0 4
  • 馬賽克 馬賽克在圖片效果中應(yīng)該是一種最常見的處理方式,日常生活中也幾乎處處可見。前段時(shí)間項(xiàng)目中要實(shí)現(xiàn)圖片馬賽克處理...
    Zed_X閱讀 7,157評論 15 19
  • 那一年,我選擇了獨(dú)立遠(yuǎn)行,火車帶著我在前進(jìn)的軌道上爬行了超過23個(gè)小時(shí); 那一年,我走過泥濘的柏油路,在那個(gè)遠(yuǎn)離故...
    木芽閱讀 1,934評論 4 5
  • 春天來了。 你怎么還不去撒野。 春天走了。 夏天就要來了。 小蝌蚪都長大了。 映山紅都開謝了。 而你將永久困在這里...
    青檸左岸閱讀 290評論 1 4

友情鏈接更多精彩內(nèi)容