Canvas探索:取色器原理

1,056 阅读3分钟

周末在家学习了一下canvas相关api,觉得getImageData这个很有意思,于是尝试了一下,豁然发现它就是获取canvas图片像素点的终极奥秘!所以写下这篇文章作为学习记录~

canvas先画个图吧

这部分代码比较简单,就直接贴了~
图片选的是本届冬奥会女子花滑个人认为最漂亮的千金~谢尔巴科娃

<!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="takeColor"></canvas>
  <script>
    const canvas = document.querySelector('#takeColor');
    const ctx = canvas.getContext('2d');
    const img = new Image();
    img.src='./谢尔巴科娃.jpeg';
    img.onload = (e) => {
      canvas.width = e.target.width;
      canvas.height = e.target.height;
      ctx.drawImage(e.target,0,0);
    }
  </script>
</body>
</html>

效果如下:

image.png 这里有一点主要注意的是:如果读取的是html本地文件,image图片读取的也是本地,那么

获取canvas图上所有像素点

这里就要借助getImageData这个API了~

    img.onload = (e) => {
      canvas.width = e.target.width;
      canvas.height = e.target.height;
      ctx.drawImage(e.target,0,0);
      const imageData = ctx.getImageData(0,0,e.target.width,e.target.height);
      console.log(imageData);
    }

获取从原点开始整张图的像素点,打印出来的数据如下:

image.png
这里的data数据类型为Uint8ClampedArray,MDN官方解释如下:

Uint8ClampedArray(8位无符号整型固定数组)  类型化数组表示一个由值固定在0-255区间的8位无符号整型组成的数组;如果你指定一个在 [0,255] 区间外的值,它将被替换为0或255;如果你指定一个非整数,那么它将被设置为最接近它的整数。(数组)内容被初始化为0。一旦(数组)被创建,你可以使用对象的方法引用数组里的元素,或使用标准的数组索引语法(即使用方括号标记)。

通俗点说Uint8ClampedArray这个类型数组下的所有元素都是0-255区间的正整数,正好对应RGB颜色值0-255,姑且把它看成一个普通的js数组吧~这个影响倒是不大。

现在我们再看一下它的长度是483600,再看canvas图的宽高是1351*900,结果是1215900,在*4那就是483600了,也就是整张图有1215900个点,每个点都有一个RGBA值(4个数值),这就是data数据长度的由来,现在所有像素点都拿到了~

开始着手取色

那么接下来就是获取点击点进行取色了,按照固有思路应该是这样的:
每4条数据为一个点,从第一行开始从左到右排列(一行高度为1px),直到最后一行。为了验证,我们可以试一下,既然是所有像素点,那么我们也可以将其放到另一个canvas里,进行图片的复制?
这里我们遍历canvas的宽高,每一个点取4个像素值 此时全部代码如下:

<!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="takeColor"></canvas>
  <canvas id="copyImg"></canvas>
  <script>
    const canvas = document.querySelector('#takeColor');
    const ctx = canvas.getContext('2d');
    const img = new Image();
    img.src='./谢尔巴科娃.jpeg';
    img.onload = (e) => {
      canvas.width = e.target.width;
      canvas.height = e.target.height;
      ctx.drawImage(e.target,0,0);
      const imageData = ctx.getImageData(0,0,e.target.width,e.target.height);

      // 将imageData放入另一个canvas里
      const copyImgCanvas = document.querySelector('#copyImg');
      copyImgCanvas.width = e.target.width;
      copyImgCanvas.height = e.target.height;
      const copyImgCtx = copyImgCanvas.getContext('2d');
      let index = 0;
      const colorData = imageData.data;
      for(let i = 0;i < canvas.height;i++){
        for(let j = 0;j < canvas.width;j++){
          const color = `rgba(${colorData[index*4 + 0]},${colorData[index*4 + 1]},${colorData[index*4 + 2]},${colorData[index*4 + 3]})`;
          copyImgCtx.fillStyle = color;
          copyImgCtx.fillRect(j,i, 1, 1);
          index++;
        }
      }
    }
  </script>
</body>
</html>

效果如下:

image.png
下边果然有一个一摸一样的千金!
那么就验证了我们的猜想,像素值是从左到右依次排开,接下来就是添加点击事件取色了!
继续用getImageData这个API获取从指定位置开始的区域的所有像素点。

// 开始取色
canvas.addEventListener('click',({ x, y }) => {
    console.log(ctx.getImageData(x,y,1,1));
})

写到这里,我们来看一下最终效果:

ezgif.com-gif-maker (1).gif 到这里取色器就实现了~