JavaScript图像点处理|吸附实现|图片处理

1. 如何获取图像的像素点

JavaScript中获取图像的相关信息主要是靠Canvas来获取,借住api getImageData来实现。

假设我们有这个一张图片

这是一张PNG-32 452 * 452的图片,现在通过getImageData拿到他上面的像素点信息

  const canvas = document.createElement('canvas');
  const ctx = canvas.getContext('2d');
  // 假设我们的图片路径是
  const source = 'http://xxx.com/test.png';
  // 创建img对象
  const img = new Image();
  // 请求资源不需要凭证,如果服务器有做特殊限制,则这段代码无效
  img.crossOrigin = 'anonymous';
  img.onload = () => {
      canvas.width = img.width;
      canvas.height = img.height;
      // 将图片绘制到canvas中,绘制起始点为x: 0, y: 0
      ctx.drawImage(img, 0, 0);
      // 获取图片像素点信息,从x: 0, y: 0开始,到图片的宽度和高度
      const imgData = ctx.getImageData(0, 0, img.width, img.height);
      console.log(imgData);
  }
  img.src = source;

上面代码打印出图片像素点的数据

红色区域当前的像素点集合452 * 452 = 204304,总像素点有204304,那为什么是817216呢,像素点集合是每四个为一个点,这四个分别代表r(0~255),g(0~255), b(0~255), a(0~255), 那么应该在原有的基础上204304 * 4 = 817216这时的像素点就对了。

2. 处理像素点
  // imgData是第一步获取到的像素点信息
  const points = imgData.data;
  const len = points.length;
  // 定义每一行row
  let row = 0;
  // 定义存储所有坐标的集合
  const imgPoints = [];
  for (let i = 0; i < len; i += 4) {
      //每4个的第0个代表r
      const r = i;
      // 每4个的第1个代表g
      const g = i + 1;
      // 每4个的第2个代表b
      const b = i + 2;
      // 每4个的第3个代表a
      const a = i + 3;
      // 把像素点转换成坐标点
      /**
      * 这里判断是否为当前行的信息,用y来做判断
      * 图片宽度是452,如果是第一行的话y为1,第二行y为2...
      * 用当前递增i的值除以4在除以图片的宽度,向上取整,计算出第几行
      */
      const y = Math.ceil(i / 4 / imgData.width);
      // 如果y与当前row不相等,也就是从下一行开始
      if (y && y !== row) {
          // 将当前y赋值给row;
          row = y;
          // 将当前行设置成集合
          imgPoints[row] = [];
      }
      // 如果当前集合存在
      if (imgPoints[row]) {
          // 如果透明度a的值存在
          if (a) {
              // 算出当前这一行上每一个像素点的位置
              const x = (i / 4) - (imgData.width * (y - 1));
              // 将x, y,当前这一行,数据结构为map
              imgPoints[row].push(
                  {
                      x,
                      y
                  }
              );
          } else {
              // 如果当前的透明值a不存在则存放0
              imgPoints[row].push(0);
          }
      }
  }

这样我们的一个数据结构已经成功了

 // 最终的数据结果,每一行都是一个集合,每一行集合里面有数据的则是map结构,没有则是0
  [
    // 第一行的坐标, y为1
     [
         {
             x: 1,
             y: 1
         },
         {
             x: 2,
             y: 1,
         },
         // 当前没有坐标点,是透明点,记录0
         0
         // ......
      ],
      // 第二行的坐标, y为2
      [
         {
             x: 1,
             y: 2
          },
          {
              x: 2,
              y: 2
          },
          // 当前没有坐标点,是透明点,记录0
          0
          // ......
      ]
  ]
2. 获取图像边缘
  // 假设我们的数据结构
  [0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0]
  // 0代表没有坐标点,1代表我们的map集合 
  // 现在我们需要将其实和结尾连续的0清除
  // 得到的结果
  [1, 1, 1, 0, 0, 1, 1, 1]
  // 现在继续将数据结构进一步优化,将第一个1, 到第一个0的位置但不包含0取出
  [1, 1, 0, 0, 1, 1, 1]
  // 继续将后面的数据结构一同取出
  [1, 1, 0, 0, 1, 1]
  // 最终将0过滤掉
  [1, 1, 1, 1]
  // 上面是我们需要的数据结构
 

  // 由此得出判断判断条件
  const f = 数组的第0个 === 1;
  const l = 数组的最后一个 === 1;
  const conditionLeft = 当前值 !== 0 && 当前值的前一个值 === 0;
  const conditionRight = 当前值 !== 0 && 当前值的后一个值 === 0;
  // 这个判断则过滤当前这一行所有的数据,并不是y,而是x
  
  // 接下来过滤y
  // 假设数据结构
  [
      [0, 0, 1, 1, 0, 0],
      [1 ,1, 0, 1, 1, 0]
  ]
  // 此时我们应该将所有列,注意:不是行,行是x,现在我们要过滤列。
  // 判断条件和行的判断条件基本一致
  const f = 第1行 === 1;
  const l = 最后1行 === 1;
  const conditionTop = 当前行的当前值的上一行对应的值 === 0 && 当前行的当前值 !== 0;
  const conditionBottom = 当前行的当前值的下一个对应的值 === 0 && 当前行的当前值 !== 0;
  // 当前判断条件将过滤所有的y

接下来我们需要将过滤x和过滤y的条件,筛选出来的数据存放在不同的数据结构中

  // 定义存放x的坐标点
  const xPoints = [];
  // 定义存放y的坐标点
  const yPoints = [];
  // imgPoints是存放所有的数据结构,是个多维数组(二维数组)
  imgPoints.forEach((points, i) => {
      points.forEach((row, j) => {
          // 首先套用x的判断
          if (
              (j === 0 && row !== 0) ||
              (j === points.length - 1 && row !== 0) ||
              (row !== 0 && typeof points[j - 1] !== 'undefined' && points[j - 1] === 0) ||
              (row !== 0 && typeof points[j + 1] !== 'undefined' && points[j + 1] === 0)
          ) {
              xPoints.push(row);
          }
          
          if (
              (i === 0 && row !== 0) ||
              (i === imgPoints[imgPoints.length - 1] && row !== 0) ||
              (typeof imgPoints[i - 1] !== 'undefined' && imgPoints[i - 1][j] === 0 && row !== 0) ||
              (typeof imgPoints[i + 1] !== 'undefined' && imgPoints[i + 1][j] === 0 && row !== 0)
          ) {
              yPoints.push(row);
          }
      })
  })
  // 上面的代码就拿到了图形边缘的所有坐标点,不管这个图形有多复杂,还是这个图形分开的多个小图形

假设我们上述不知道,并且我们的程序中不知道这个图形的颜色。推荐大家使用第三方的库,可以转换成边缘图像。
本人推荐https://github.com/miguelmota/sobel,这个库的代码很少,有兴趣可以阅读一下他里面的核心算法。我还使用了一些其他的图像转边缘图像的其他库,但出来的效果并不是很好。

3. 图像吸附

当你拿到边缘点的时候,你就可以对你的图像进行吸附的功能操作。吸附功能还要根据各自的业务去实现,在这里不在coding。

发表评论