一塊兒擼個組件庫(二):帶妹上分之擼個<你們來找茬>'輔助'工具

上一篇: 一塊兒擼個組件庫(一):拖拽API實戰之拖拽組件css

hello,你們好。咱們繼續來擼組件,此次咱們一塊兒擼個花裏胡哨的組件,那就是你們來找茬的輔助工具。最初的點子來自黃軼老師粉絲羣,老師再玩找茬,而後截了個圖出來,底下除了喊666,不知道說什麼,第一次感受到了技術離生活這麼近。以爲很cool,因而本身研究了一番,終於實現了,在此分享給你們。vue

在此感謝 liuyubobobo老師canvas課程,學到不少知識點,開了眼界,原來 canvas還能夠這麼玩。話很少說,咱們趕忙動手來實現它吧。

找茬實現步驟,以後一一詳細說明:

1. 獲取截圖數據

2. 找到關鍵點

3. 對比兩張圖

4. 呈現到頁面

1. 獲取截圖數據

1.1獲取Ctrl + v的圖像數據

找茬拼的就是速度,因此截圖以後立刻Ctrl + v就須要獲得圖像的數據進行比較。首先寫出模板和對應的事件:git

<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增長點擊事件,點擊任意的地方均可以得到輸入框焦點,一切爲了速度!github

1.2繪製到canvas

而後咱們把這個獲得數據轉成base64畫到canvas上去:npm

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軸位置,後面是繪製的大小。canvas

2.找到關鍵點

2.1確認目標點的像素信息

這裏先把這個工具的核心實現原理交代了,其實並不複雜,就是使用canvas獲得整張大圖的全部像素信息,而後對比裏面兩張小圖裏每個像素的RGB值,以此找出不一樣的地方。數組

但每張圖片的截圖位置確定是不一樣的,不過通過觀察,咱們也能夠發現不少有規律的地方,兩張小圖是處於同一個水平線的,以及它們的高寬是相同的,它們間距是相同的,最後它們周圍的背景是相同的。因此咱們如今的第一步就是要知道左邊小圖的左上角在哪裏。因而我截了張圖放到了ps裏面,並將它的左上角放到了最大:bash

經過吸管取值,發現和圖片相接觸的幾個點它們的 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課程裏的截圖:app

這裏解釋下,從 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像素範圍。

以前說明了,遍歷的 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>
複製代碼

最後

相關文章
相關標籤/搜索