一起撸个vue组件库(二):大家来找茬辅助工具

算法灯塔说
• 阅读 2562

hello,大家好。我们继续来撸组件,这次我们一起撸个花里胡哨的组件,那就是大家来找茬的辅助工具。最初的点子来自黄轶老师粉丝群,老师再玩找茬,然后截了个图出来,底下除了喊666,不知道说什么,第一次感觉到了技术离生活这么近。觉得很cool,于是自己研究了一番,终于实现了,在此分享给大家。

一起撸个vue组件库(二):大家来找茬辅助工具
在此感谢liuyubobobo老师的canvas课程,学到很多知识点,开了眼界,原来canvas还可以这么玩。话不多说,我们赶紧动手来实现它吧。

找茬实现步骤,之后一一详细说明:

1. 获取截图数据

2. 找到关键点

3. 对比两张图

4. 呈现到页面

1. 获取截图数据

1.1获取Ctrl + v的图像数据

找茬拼的就是速度,所以截图之后马上Ctrl + v就需要得到图像的数据进行比较。首先写出模板和对应的事件:

<template>
  <div>
    <input 
      @paste="pasteImgDate"  // 输入框聚焦时执行ctrl + v后触发的事件
      @blur="onblur"
      readonly
      ref="input" 
      placeholder="ctrl+v复制截图" 
    />
    <span style="color: red;">{{tips}}</span>  // 提示语
  </div>
</template>

export default {
  data() {
    return {
      tips: ''
    }
  },
  mounted() {
    document.addEventListener("click", this.getFocus);  // 点击空白聚焦,为了速度!
  },
  beforeDestroy() {
    document.removeEventListener("click", this.getFocus);
  },
  methods: {
    getFocus() {
      this.$refs['input'].focus();  
    },
    pasteImgDate(e) {
      const file = e.clipboardData.items[0].getAsFile();  // 得到图像数据
      if(!file) {
        this.tips = "没有可以复制的数据";
        return;
      }
      ...
    },
    onblur() {
      this.tips = ''
    }
  }
}

首先我们设置一个tips变量用于提示操作中遇到的问题,然后我们监听paste事件,对聚焦输入框按下Ctrl + v后会触发这个事件,在这个事件对象里面就可以拿到对应截图的数据,相当于就是file类型的input选择了一张图片,给document增加点击事件,点击任意的地方都可以获得输入框焦点,一切为了速度!

1.2绘制到canvas

然后我们把这个得到数据转成base64画到canvas上去:

methods: {
  pasteImgDate(e) {
    ...
    const reader = new FileReader();
      reader.readAsDataURL(file);  // 读取图像数据
      reader.onload = e => {
        const img = new Image();
        img.src = e.target.result;  // 得到转化后的base64格式
        img.onload = () => {
          const canvas = document.createElement("canvas"); // 创建一个canvas标签
          const ctx = canvas.getContext("2d");
          const width = img.width;
          const height = img.height;
          canvas.width = width;
          canvas.height = height;
          ctx.drawImage(img, 0, 0, width, height);  // 将图片绘制到canvas标签里
        }
      }
  }
}

既然得到图片数据就好办了,使用new FileReader读取文件,然后转换化base64格式,赋值给一个空的img标签,监听它的onload事件,最后使用drawImage这个API将这个图片绘制到canvas标签上,函数里的0,0表示就是绘制启始的xy轴位置,后面是绘制的大小。

2.找到关键点

2.1确认目标点的像素信息

这里先把这个工具的核心实现原理交代了,其实并不复杂,就是使用canvas得到整张大图的所有像素信息,然后对比里面两张小图里每一个像素的RGB值,以此找出不同的地方。

但每张图片的截图位置肯定是不同的,不过经过观察,我们也可以发现很多有规律的地方,两张小图是处于同一个水平线的,以及它们的高宽是相同的,它们间距是相同的,最后它们周围的背景是相同的。所以我们现在的第一步就是要知道左边小图的左上角在哪里。于是我截了张图放到了ps里面,并将它的左上角放到了最大:

一起撸个vue组件库(二):大家来找茬辅助工具
通过吸管取值,发现和图片相接触的几个点它们的RGB值都是80, 148, 176,好嘞,找到了,我们从大图的x轴开始一行行的找,第一个点肯定就是这里了。接下来编写之后的代码:

methods: {
  pasteImgDate(e) {
    ...
    ctx.drawImage(img, 0, 0, width, height); //之前代码
    
    const imgData = ctx.getImageData(0, 0, width, height);
    const pixelData = imgData.data;
  }
}

2.2通过遍历根据条件找

之前使用drawImage把图片画到了canvas里,现在我们通过getImageData读取这个canvas里面的数据,里面的像素值就保存在data属性里面。它们的排列是一个一维数组,放一张liuyubobobo老师canvas课程里的截图:

一起撸个vue组件库(二):大家来找茬辅助工具
这里解释下,从canvas里读取的像素值是一维数组排列的,每四个值表示一个像素的RGBA。所以这里就会有两种遍历这个数组的方式:

第一种就是单循环的顺序遍历,从第一个节点开始依次挨个遍历到最后一个像素点,就像这样:

for(let i = 0; width * height; i++) {
 const r = pixelData[4 * i + 0]; // i像素的r通道值
 const g = pixelData[4 * i + 1]; // i像素的g通道值
 const b = pixelData[4 * i + 2]; // i像素的b通道值
}

第二种就是双循环的顺序遍历,可知道遍历到了某行某列,就像这样:

for(let y = 0; y < height; y++) {
  for(let x = 0; x < width; x++) {
    const p = y * width + x;  // 得到y列x行
    const r = pixelData[p * 4 + 0]; // y列x行像素的r通道值
    const g = pixelData[p * 4 + 1]; // y列x行像素的g通道值
    const b = pixelData[p * 4 + 2]; // y列x行像素的b通道值
  }
}

这里如果改变某一个像素的RGB值,然后将改变后的数组重新放到canvas里,就生成了一张新的图像,知道这个后,就可以实现非常多有意思的滤镜。接下来我们使用第二种循环的方式,找出那个关键点来:

export default {
  created() {
    this.imgPos = {};  // 记录找到点的位置
  },
  methods: {
    handleChenge(e) {
    ...
      for (let y = 0; y < 200; y++) {  // 设置200的目的为缩小范围检索
        for (let x = 0; x < 200; x++) {
          function rgbAddUp(pos) {
            return (
              pixelData[4 * pos + 0] +
                pixelData[4 * pos + 1] +
                pixelData[4 * pos + 2] ===
              404  // 80 + 148 + 176   404? 是不是故意的
            );
          }
          const p = rgbAddUp(y * img.width + x);  // 当前点
          const top = rgbAddUp((y - 1) * img.width + x);  // 上面的点
          const right = rgbAddUp(y * img.width + x + 1);  // 右边的点
          const bottom = rgbAddUp((y + 1) * img.width + x);  // 下面的点
          const left = rgbAddUp(y * img.width + x - 1);  // 左边的点
          const rightTop = rgbAddUp((y - 1) * img.width + x + 1);  // 右上的点
          const leftBottom = rgbAddUp((y + 1) * img.width + x - 1);  // 坐下的点
          if (
            p &&
            top &&
            left &&
            bottom &&
            rightTop &&
            leftBottom &&
            !right
          ) {
            if (!this.imgPos.y && !this.imgPos.x) {
              this.imgPos.y = y;
              this.imgPos.x = x;
              break;
            }
          }
        }
        if (this.imgPos.y && this.imgPos.x) {
          break;
        }
      }
    }
  }
}

这里为什么设置200是为了缩小遍历的范围,毕竟像素点太多,避免页面出现停顿,所以就要求截图会有点要求,只会遍历截图左上角200像素范围。

一起撸个vue组件库(二):大家来找茬辅助工具
之前说明了,遍历的yx就分别表示的是当前的列和行交叉的点,所以我们可以得到这个点它周围点的像素信息,如果它周围的点的RGB通道相加等于404,也就是图片中标记的那几个点,且右边不是的,这样的xy就是我们想要的,找到后,退出循环。

3. 对比两张图

3.1 找到符合的点

这个时候找到关键点了,接下来提供几个ps测量到的固定数据给到大家。小图的高是286,宽是381,第一张最左边到第二张最左边距离是457。有了关键点,有了这些固定的值,我们就可以同时遍历两张图片的像素信息,找出它们不同地方了:

pasteImgDate(e) {
  ...
  for (let y = this.imgPos.y; y < 286 + this.imgPos.y; y++) {
    for (let x = this.imgPos.x; x < 381 + this.imgPos.x; x++) {
      if (
        pixelData[(y * img.width + x) * 4 + 0] + 10 >
        pixelData[(y * img.width + x + 457) * 4 + 0] &&
        pixelData[(y * img.width + x) * 4 + 1] + 10 >
        pixelData[(y * img.width + x + 457) * 4 + 1] &&
        pixelData[(y * img.width + x) * 4 + 2] + 10 >
        pixelData[(y * img.width + x + 457) * 4 + 2]
      ) {
        pixelData[(y * img.width + x + 457) * 4 + 0] = 0;
        pixelData[(y * img.width + x + 457) * 4 + 1] = 0;
        pixelData[(y * img.width + x + 457) * 4 + 2] = 0;
      }
    }
  }
}

为什么不用!==来判断两个像素点的区别,因为两张图片不是只有找茬的地方不同,通过机器去计算发现,有太多不同的地方了,都有很小的像素波动的地方,所以使用!==并不能很准确的反映到找到的图片上。所以换个条件找,把相同点的RGB都设置成0,也就是设置成黑色。

4. 呈现到页面

4.1 添加到canvas里

像素信息已经被修改了,现在我们将它放到canvas标签里,然后将canvas标签放到body内即可。

pasteImgDate(e) {
  ...
  if (!this.imgPos.y && !this.imgPos.x) {
    this.tips = "截图不符合";
    return;
  }
  delete this.imgPos.y;  // 移除
  delete this.imgPos.x;
  const canDraw = document.getElementById("__canvas_diff_");
  canDraw && document.body.removeChild(canDraw);
  const canvas2 = document.createElement("canvas");
  const ctx2 = canvas2.getContext("2d");
  canvas2.id = "__canvas_diff_";
  canvas2.width = width;
  canvas2.height = height;
  ctx2.putImageData(imgData, 0, 0, 0, 0, width, height);  // 将数据放入到canvas里
  document.body.appendChild(canvas2);
}

组件安装

npm i vue-gn-components

import { FindDIff } from 'vue-gn-components';
import "vue-gn-components/lib/style/index.css";
Vue.use(FindDIff)

组件调用

<template>
  <find-diff />
</template>

最后

  • 如果使用qq截图工具,请保证截图是png格式,因为jpg的像素会有损压缩。第一次可以先保存一张png到本地,以后每次就记住你的选择。
  • 源码所在地 >>> vue-gn-components。觉得还行,请给个start吧。
点赞
收藏
评论区
推荐文章
blmius blmius
4年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
美凌格栋栋酱 美凌格栋栋酱
7个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Peter20 Peter20
4年前
mysql中like用法
like的通配符有两种%(百分号):代表零个、一个或者多个字符。\(下划线):代表一个数字或者字符。1\.name以"李"开头wherenamelike'李%'2\.name中包含"云",“云”可以在任何位置wherenamelike'%云%'3\.第二个和第三个字符是0的值wheresalarylike'\00%'4\
Wesley13 Wesley13
3年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Easter79 Easter79
3年前
TurnipBit开发板DIY呼吸的吃豆人教程实例
  转载请以链接形式注明文章来源(MicroPythonQQ技术交流群:157816561,公众号:MicroPython玩家汇)  0x00前言  吃豆人是耳熟能详的可爱形象,如今我们的TurnipBit也集成了这可爱的图形,我们这就让他来呼吸了~。  0x01效果展示  先一起看下最终的成品演示视频:  http:/
Wesley13 Wesley13
3年前
PHP创建多级树型结构
<!lang:php<?php$areaarray(array('id'1,'pid'0,'name''中国'),array('id'5,'pid'0,'name''美国'),array('id'2,'pid'1,'name''吉林'),array('id'4,'pid'2,'n
Wesley13 Wesley13
3年前
Java日期时间API系列36
  十二时辰,古代劳动人民把一昼夜划分成十二个时段,每一个时段叫一个时辰。二十四小时和十二时辰对照表:时辰时间24时制子时深夜11:00凌晨01:0023:0001:00丑时上午01:00上午03:0001:0003:00寅时上午03:00上午0
Python进阶者 Python进阶者
1年前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这