上一篇: 一塊兒擼個組件庫(一):拖拽API實戰之拖拽組件css
hello,你們好。咱們繼續來擼組件,此次咱們一塊兒擼個花裏胡哨的組件,那就是你們來找茬的輔助工具。最初的點子來自黃軼
老師粉絲羣,老師再玩找茬,而後截了個圖出來,底下除了喊666
,不知道說什麼,第一次感受到了技術離生活這麼近。以爲很cool
,因而本身研究了一番,終於實現了,在此分享給你們。vue
canvas
課程,學到不少知識點,開了眼界,原來
canvas
還能夠這麼玩。話很少說,咱們趕忙動手來實現它吧。
1. 獲取截圖數據
2. 找到關鍵點
3. 對比兩張圖
4. 呈現到頁面
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
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
表示就是繪製啓始的x
,y
軸位置,後面是繪製的大小。canvas
這裏先把這個工具的核心實現原理交代了,其實並不複雜,就是使用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;
}
}
複製代碼
以前使用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
像素範圍。
y
和
x
就分別表示的是當前的列和行交叉的點,因此咱們能夠獲得這個點它周圍點的像素信息,若是它周圍的點的
RGB
通道相加等於
404
,也就是圖片中標記的那幾個點,且右邊不是的,這樣的
x
和
y
就是咱們想要的,找到後,退出循環。
這個時候找到關鍵點了,接下來提供幾個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,也就是設置成黑色。
像素信息已經被修改了,如今咱們將它放到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
吧。
已有組件集合 >>> 你可能會用的上的一個vue功能組件庫,持續完善中...