canvas实现动态替换人物的背景颜色

起因

makefile 复制代码
今天遇见一个特别有意思的小功能。
就是更换人物图像的背景颜色。
大致操作步骤就是:点击人物-实现背景颜色发生变化

将图片绘画到canvas画布上

css 复制代码
我们需要将图片绘制到canvas画布上。
这样做的目的是为了方便我们去操作像素点来更改颜色。
首先创建 Image 的实例。将图片的地址赋值给图片实例src。
当图片加载完成后,onload 事件可以知道图片是否加载完成
根据 Image的实例将图片大小赋值给画布,让他们大小保持一致。
最后使用 ctx.drawImage来进行绘画就行
特别提醒的是:src 属性一定要写到 onload 的后面,否则程序在 IE 中会出错。
xml 复制代码
<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 的简单介绍

scss 复制代码
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:允许任何的画布图像源

注册事件获取点击时的坐标对应的颜色

arduino 复制代码
我们通过 e.offsetX, e.offsetY 可以轻松拿到点击的坐标x,y。
可以通过 getImageData 获取到图片的所有像素点的颜色。
但是怎么通过点击的位置(x,y)获取到对应的的像素索引呢?
其实他们的关系是这样的:
// 每个像素占用4个字节(RGBA)
const index = (y * image.width + x) * 4; 
根据上面这个公式,我们可以知道坐标对应的像素索引。
有了索引,我们可以拿到坐标对应的颜色
javascript 复制代码
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)

更改当前像素点的颜色

scss 复制代码
现在我们希望点击的这个点的颜色变成红色。
现在的我们可以拿到所有像素点,当前的坐标,坐标对应的颜色。
现在我们的主角出场了(此时灯光闪烁,五彩的光打在他的身上)
context.putImageData(imageData, x, y);
第1个参数:imageData: 包含了图像的所有像素数据,
通过ctx.getImageData(0, 0, canvas.width, canvas.height)可以获取到;
第2,3个参数表示坐标。
它用于将图像数据绘制到画布上。
这个方法允许开发者操作和绘制像素级别的数据,
从而实现复杂的图像效果和处理。
scss 复制代码
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的各个绝对值之和。
来判断颜色的相似。
同时我页需要注意边界范围与颜色已经变为了目标颜色。
这个时候我们就需要停止调用函数了

核心代码

xml 复制代码
<!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>

更改为按钮,背景发生改变

scss 复制代码
上面我们实现了,点击背景色,实现颜色的更改。
但是实际的过程中。
我们是不知道背景颜色的,怎么去确认背景颜色呢?
其实,可以默认坐标为(4,4)是背景颜色。
在实际的过程中,其实这个位置99.9999%是背景色。
我们是先选择颜色,然后点击确定,实现颜色的更改
现在我们来优化一下。让用户自己选择背景色,选择好后。
点击确定,背景颜色就发生变化
xml 复制代码
<!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>

最后的功能-下载

css 复制代码
上面我们已经成功实现让用户选择颜色。
更换用户自己选择的颜色。
下载我们只需要实现下载功能就好了。
下载功能主要使用 canvas.toDataURL 
然后利用a标签进行下载
ini 复制代码
<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);
})
相关推荐
卸任3 小时前
Vibe coding工具使用心得
前端·ai编程
brien94 小时前
为什么模型标称 1M,Google Antigravity 聊到 256K 却突然失忆?我们给它做了个原生行内监控圆环
前端
修炼的dance4 小时前
录屏关键帧间隔多少合适?六档实测,2秒到10秒小了75.6%
前端
Lank_M4 小时前
关键帧从1秒拉到10秒,录屏小87.3%,摄像头小10.8%
前端
补码缩写4 小时前
用 ffprobe 拆录屏,八成字节在 29 个关键帧上
前端
百思不得小李4 小时前
一个复杂的动态表单是如何实现的
前端·设计模式
陈随易5 小时前
Cmder 停更 17 个月,2.0 支持 Windows Terminal
前端·后端·程序员
清晨Coding5 小时前
删了 Cookie 还能自动登录?Google系网站登录态自动恢复的原理分析
javascript
风骏时光牛马5 小时前
前沿视界
前端
kyriewen5 小时前
我扫了 814 个依赖,97 个还敢要 Node 10
前端·javascript·程序员