canvas實現動態替換人物的背景顏色

2023-11-17 12:01:29

起因

今天遇見一個特別有意思的小功能。
就是更換人物影象的背景顏色。
大致操作步驟就是:點選人物-實現背景顏色發生變化

將圖片繪畫到canvas畫布上

我們需要將圖片繪製到canvas畫布上。
這樣做的目的是為了方便我們去操作畫素點來更改顏色。
首先建立 Image 的範例。將圖片的地址賦值給圖片範例src。
當圖片載入完成後,onload 事件可以知道圖片是否載入完成
根據 Image的範例將圖片大小賦值給畫布,讓他們大小保持一致。
最後使用 ctx.drawImage來進行繪畫就行
特別提醒的是:src 屬性一定要寫到 onload 的後面,否則程式在 IE 中會出錯。
<body>
  <canvas id="canvas">
 </body>
 <script type="text/javascript">
  // 獲取dom節點
  const  canvas = document.getElementById('canvas')
  //獲取上下文
  const ctx = canvas.getContext('2d');
  // 將圖片繪製到canvas畫布上
  function initPic(picInfo){
    // 建立一個圖片的範例
    const img = new Image()
    // 引入圖片的地址
    img.src = picInfo.url 
    img.onload =()=>{
      // 設定畫布的寬高與圖片的保持一致
      canvas.width= img.width
      canvas.height= img.height
      // 開始繪畫
      ctx.drawImage(img, 0, 0 );
    }
  }
  initPic({
    url: './src/assets/person.png'
  })
 </script>

drawImage 的簡單介紹

canvas的drawImage()提供了更多在canvas上繪製影象的方法。
drawImage() 方法有三種形式:
drawImage(image, dx, dy)  在指定的 (dx, dy) 位置繪製影象。
drawImage(image, dx, dy, width, height)  在指定的 (dx, dy) 位置,並使用指定的寬度和高度繪製影象。
drawImage(image, sourceX, sourceY, sourceWidth, sourceHeight, dx, dy, width, height) 在指定的 (dx, dy) 位置,並使用指定的寬度和高度繪製影象。影象的源座標和尺寸也指定了。
image:允許任何的畫布影象源

註冊事件獲取點選時的座標對應的顏色

我們通過 e.offsetX, e.offsetY 可以輕鬆拿到點選的座標x,y。
可以通過 getImageData 獲取到圖片的所有畫素點的顏色。
但是怎麼通過點選的位置(x,y)獲取到對應的的畫素索引呢?
其實他們的關係是這樣的:
// 每個畫素佔用4個位元組(RGBA)
const index = (y * image.width + x) * 4; 
根據上面這個公式,我們可以知道座標對應的畫素索引。
有了索引,我們可以拿到座標對應的顏色
function clickMy(e){
  // 獲取點選時的座標
  let x = e.offsetX
  let y = e.offsetY
  // 獲取所有的畫素點顏色
  let imagedata = ctx.getImageData(0, 0, canvas.width, canvas.height);
  console.log('獲取所有的畫素點顏色', imagedata)
  // 這個座標點對應的顏色
  let clickColor = getColor(x,y, imagedata)
  console.log('這個座標點對應的顏色', clickColor)
}
// 計算點選座標對應的畫素索引 
function bgIndex(x,y){
  return (y * canvas.width + x) * 4;
}
// 根據索引得到顏色值
function getColor(x,y,imgData){
  let i = bgIndex(x,y)
  return [
    imgData.data[i],
    imgData.data[i+1],
    imgData.data[i+2],
    imgData.data[i+3]
  ]
}

// 註冊事件
canvas.addEventListener("click", clickMy, false)

更改當前畫素點的顏色

現在我們希望點選的這個點的顏色變成紅色。
現在的我們可以拿到所有畫素點,當前的座標,座標對應的顏色。
現在我們的主角出場了(此時燈光閃爍,五彩的光打在他的身上)
context.putImageData(imageData, x, y);
第1個引數:imageData: 包含了影象的所有畫素資料,
通過ctx.getImageData(0, 0, canvas.width, canvas.height)可以獲取到;
第2,3個參數列示座標。
它用於將影象資料繪製到畫布上。
這個方法允許開發者操作和繪製畫素級別的資料,
從而實現複雜的影象效果和處理。
function clickMy(e){
  // 獲取點選時的座標
  let x =e.offsetX
  let y = e.offsetY
  // 獲取所有的畫素點顏色
  let imagedata = ctx.getImageData(0, 0, canvas.width, canvas.height);
  console.log('獲取所有的畫素點顏色', imagedata)
  // 這個座標點對應的顏色
  let clickColor = getColor(x,y, imagedata)
  console.log('這個座標點對應的顏色', clickColor)
  // 最後更改為紅色的rgba值
  let targetBgArr = [255,0,0,255]
  // 更改顏色
  function changeColor(x,y){
    let i = bgIndex(x,y)
    imagedata.data.set(targetBgArr, i)
  }
  changeColor(x,y)
  // 更改當前畫素點的顏色
  ctx.putImageData(imagedata, 0, 0);
}

將被點選的點的相似顏色全部變為紅色

我們通過兩個顏色的rgba值相減,看rgba的各個絕對值之和。
來判斷顏色的相似。
同時我頁需要注意邊界範圍與顏色已經變為了目標顏色。
這個時候我們就需要停止呼叫函數了

核心程式碼

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <canvas id="canvas">
 </body>
 <script type="text/javascript">
  // 獲取dom節點
  const  canvas = document.getElementById('canvas')
  //  獲取上下文
  const ctx = canvas.getContext('2d',{
    willReadFrequently:true
  });
  function initPic(picInfo){
    // 建立一個圖表的範例
    const img = new Image()
    img.onload =()=>{
      // 設定畫布的寬高與圖片的保持一致
      canvas.width= img.width
      canvas.height= img.height
      // 開始繪畫
      ctx.drawImage(img, 0, 0 );
    }
    // 引入圖片的地址
    img.src = picInfo.url 
  }
 
  function clickMy(e){
    // 獲取點選時的座標
    let x =e.offsetX
    let y = e.offsetY
    // 獲取所有的畫素點顏色
    let imagedata = ctx.getImageData(0, 0, canvas.width, canvas.height);
    console.log('獲取所有的畫素點顏色', imagedata)
    // 這個座標點對應的顏色
    let clickColor = getColor(x,y, imagedata)
    console.log('這個座標點對應的顏色', clickColor)
    // 最後更改為紅色的rgba值
    let targetBgArr = [255,0,0,255]
    function changeColor(x,y){
      // 邊界範圍
      if(x<0 || x>canvas.width || y<0 || y>canvas.height){
        return
      }
      let color = getColor(x,y,imagedata )
      // 相似顏色的相差值
      if(diffBg(color,clickColor)>150){
        return
      }
      // 已經變為了目標色(紅色)
      if(diffBg(color,targetBgArr)==0){
        return
      }
      let i = bgIndex(x,y)
      // 在記憶體中更改畫素的顏色
      imagedata.data.set(targetBgArr, i)
      // 改變周圍(上下左右)的顏色
      changeColor(x+1,y)
      changeColor(x-1,y)
      changeColor(x,y+1)
      changeColor(x,y-1)
    }
    changeColor(x,y)
    // 將記憶體中的畫素點的顏色(重新繪製在畫布上)
    ctx.putImageData(imagedata, 0, 0);
  }
  // 計算點選座標對應的畫素索引 
  function bgIndex(x,y){
    return (y * canvas.width + x) * 4;
  }
  
  // 根據索引得到顏色值
  function getColor(x,y,imgData){
    let i = bgIndex(x,y)
    return [
    imgData.data[i],
    imgData.data[i+1],
    imgData.data[i+2],
    imgData.data[i+3]
    ]
  }
  // 檢視兩個顏色的相差值
  function diffBg(color1,color2){
    // 我們是取兩個顏色的絕對值相加
    return Math.abs(color1[0] -color2[0]) +
      Math.abs(color1[1] -color2[1]) +
      Math.abs(color1[2] -color2[2]) +
      Math.abs(color1[3] -color2[3]) 
  }
  // 註冊事件
  canvas.addEventListener("click", clickMy, false)

  initPic({
    url: '../assets/person1.png'
  })
 </script>
</html>

更改為按鈕,背景發生改變

上面我們實現了,點選背景色,實現顏色的更改。
但是實際的過程中。
我們是不知道背景顏色的,怎麼去確認背景顏色呢?
其實,可以預設座標為(4,4)是背景顏色。
在實際的過程中,其實這個位置99.9999%是背景色。
我們是先選擇顏色,然後點選確定,實現顏色的更改
現在我們來優化一下。讓使用者自己選擇背景色,選擇好後。
點選確定,背景顏色就發生變化
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
  </style>
</head>
<body>
  <div>
    <canvas id="canvas">
  </div>
  <input type="color" id="color" >
  <button id="red">確定</button>
 </body>
 <script type="text/javascript">
  // 獲取dom節點
  const  canvas = document.getElementById('canvas')
  //  獲取上下文
  const ctx = canvas.getContext('2d',{
    willReadFrequently:true
  });
  // 將16進位制轉化為rgba的顏色值
  function changeRGBA(hex) {  
    // 去除 # 開頭的第一個字元  
    hex = hex.slice(1);  
    // 將16進位制字串轉換rgba
    let rgba = [];  
    for (let i = 0; i < 6; i += 2) {  
      let byte = parseInt(hex.substr(i, 2), 16);  
      rgba.push(byte);  
    }  
    // 新增 alpha 通道
    rgba.push(255);  
    // 返回 RGBA 顏色值  
    return rgba;  
  }
  function initPic(picInfo){
    // 建立一個圖表的範例
    const img = new Image()
    img.onload =()=>{
      // 設定畫布的寬高與圖片的保持一致
      canvas.width= img.width
      canvas.height= img.height
      // 開始繪畫
      ctx.drawImage(img, 0, 0 );
    }
    // 引入圖片的地址
    img.src = picInfo.url 
  }
 
  function clickMy(e, type){
    let color = document.getElementById('color')
    // 4,4的地方預設為是背景顏色
    let x = 4
    let y = 4
    // 獲取所有的畫素點顏色
    let imagedata = ctx.getImageData(0, 0, canvas.width, canvas.height);
    // 這個座標點對應的顏色
    let clickColor =  getColor(x,y, imagedata)
    console.log('這個座標點對應的顏色', clickColor)
    // 顏色為使用者選擇的值
    let targetBgArr = changeRGBA(color.value)
    function changeColor(x,y){
      // 邊界範圍
      if(x<0 || x>canvas.width || y<0 || y>canvas.height){
        return
      }
      let color = getColor(x,y,imagedata )
      // 相似顏色的相差值
      if(diffBg(color,clickColor)>150){
        return
      }
      // 已經變為了目標色(紅色)
      if(diffBg(color,targetBgArr)==0){
        return
      }
      let i = bgIndex(x,y)
      // 在記憶體中更改畫素的顏色
      imagedata.data.set(targetBgArr, i)
      // 改變周圍(上下左右)的顏色
      changeColor(x+1,y)
      changeColor(x-1,y)
      changeColor(x,y+1)
      changeColor(x,y-1)
    }
    changeColor(x,y)
    // 將記憶體中的畫素點的顏色(重新繪製在畫布上)
    ctx.putImageData(imagedata, 0, 0);
  }
  // 計算點選座標對應的畫素索引 
  function bgIndex(x,y){
    return (y * canvas.width + x) * 4;
  }
  // 根據索引得到顏色值
  function getColor(x,y,imgData){
    let i = bgIndex(x,y)
    return [
    imgData.data[i],
    imgData.data[i+1],
    imgData.data[i+2],
    imgData.data[i+3]
    ]
  }
  // 檢視兩個顏色的相差值
  function diffBg(color1,color2){
    // 我們是取兩個顏色的絕對值相加
    return Math.abs(color1[0] -color2[0]) +
      Math.abs(color1[1] -color2[1]) +
      Math.abs(color1[2] -color2[2]) +
      Math.abs(color1[3] -color2[3]) 
  }
  // 註冊事件
  canvas.addEventListener("click", clickMy, false)
  red.addEventListener("click", clickMy, false)
  initPic({
    url: '../assets/person1.png'
  })
 </script>
</html>

最後的功能-下載

上面我們已經成功實現讓使用者選擇顏色。
更換使用者自己選擇的顏色。
下載我們只需要實現下載功能就好了。
下載功能主要使用 canvas.toDataURL 
然後利用a標籤進行下載
<button id="down">下載</button>
down.addEventListener('click',()=>{
  let imgURL = canvas.toDataURL({format: "image/png", quality:1, width:canvas.width, height:canvas.height});
  let link = document.createElement('a');
  link.download = "人物圖片";
  link.href = imgURL;
  document.body.appendChild(link);
  link.click();
  document.body.removeChild(link);
})