首页 > 其他分享 >canvas实现动态替换人物的背景颜色

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

时间:2023-11-17 09:22:23浏览次数:23  
标签:function canvas 颜色 imagedata width let 动态 替换

起因

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

将图片绘画到canvas画布上

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

标签:function,canvas,颜色,imagedata,width,let,动态,替换
From: https://www.cnblogs.com/IwishIcould/p/17834419.html

相关文章

  • 54. 替换数字(卡码网 第八期模拟笔试)
    2023-11-16题目页面(kamacoder.com)思路:        如果是c++,字符串可以改变,考虑双指针        但是Java的字符串不可变,所以就是按照题目的意思完成就行importjava.util.Scanner;classMain{publicstaticvoidmain(String[]args){/......
  • @WebServiceClient wsdlLocation 动态给注解内容参数赋值
    动态给注解内容参数赋值@WebServiceClient(name="IXxxService",targetNamespace="http://xxx.xxx.xxx.com",wsdlLocation="${WSDL_URL}")publicclassIXxxServiceextendsService{ //静态变量在静态代码块加载后加载,且注解也在之后加载,完成动态注入修改注解里的参......
  • C#调用C++动态库接口函数和回调函数方法 后续
    声明回调委托,C#的委托可以实现C#调用C++的回调,操作函数以后的回调//定义委托,CallingConvention.StdCall可以,CallingConvention.Cdecl不行,参考https://www.it1352.com/1792610.html//[UnmanagedFunctionPointer(CallingConvention.Cdecl)]//不需要要添加该句话,具体参考//htt......
  • iframe本身就不是动态语言,样式和脚本都需要额外导入.iFrame的本质是内联框架的缩写,它
    以下哪个选项的描述是错误的Aiframe是用来在网页中插入第三方页面,早期的页面使用iframe主要是用于导航栏这种很多页面都相同的部分,这样在切换页面的时候避免重复下载Biframe的创建比一般的DOM元素慢了1-2个数量级Ciframe标签会阻塞页面的的加载Diframe本质是动态语言的Inc......
  • C#调用C++动态库接口函数和回调函数方法
    这篇文章主要介绍了C#调用C++动态库接口函数和回调函数方法,通过C++端编写接口展开内容,文章介绍详细具有一定的参考价值,需要的小伙伴可以参考一下需求: 当前C已经写好了一个动态库,完成了产品开发需求,C#需要调用C编写的动态库DLL接口,开发出完整的软件,DLL动态库里包含了普通接口函......
  • canvas绘制圆环
    初识canvascanvas我们可以理解为是一个画布。它是一个载体。我们的文字,图案,都是在这个载体(画布)上来进行操作的。canvas的5个要素canvas具有的5个要素:1.id元素的唯一标识2.width宽度3.height高度4.画笔,上下文canvas.getContext('2d')5.内容(文字,图形,其他)can......
  • 【动态规划】矩阵连乘问题
    问题描述:给定n个矩阵{A1,A2,…,An},其中Ai与Ai+1是可乘的,i=1,2…,n-1。如何确定计算矩阵连乘积的计算次序,使得依此次序计算矩阵连乘积需要的数乘次数最少。m[i][j] :i=j时指矩阵Ai ,i<j时指矩阵Ai到矩阵Aj的若干矩阵连乘的最小次数。pi指维数。例: ......
  • DyHGCN:一种学习用户动态偏好的动态异构图卷积网络,用于信息扩散预测
    DyHGCN:ADynamicHeterogeneousGraphConvolutionalNetworktoLearnUsers’DynamicPreferencesforInformationDiffusionPredictionECML-PKDD2020欧洲机器学习与数据挖掘顶级会议Abstract​ 信息扩散预测是了解信息传播过程的一项基本任务。它在错误信息传播预测......
  • 【路径规划】基于动态窗口法DWA算法的机器人动态避障路径规划研究附Matlab代码
    ✅作者简介:热爱科研的Matlab仿真开发者,修心和技术同步精进,代码获取、论文复现及科研仿真合作可私信。......
  • 第8章 Qt 布局管理--动态调整浏览器登录
    Qt布局管理--动态调整浏览器登录控件没有跟随窗口变大的位置布局管理器概念及原理讲解参考值:控件变化的最小限度扩展策略:水平垂直扩展时扩展的方案都拉伸还是只拉伸,还有保持不变的方案比利:多个控件分别按多少比利1:1间隙:美观边距:美观这块吃实践,暂时放着··......