在實(shí)際的業(yè)務(wù)中需要對(duì)圖像做特殊處理柑土,比如獲取圖像的像素點(diǎn),獲取圖像的邊緣信息绊汹,圖像和圖像之間需要做到吸附稽屏。有的小伙伴不知道如何操作,那么這篇文章會(huì)給你帶來一定的收獲西乖。知道的小伙伴或者有更簡(jiǎn)單的方法狐榔,還請(qǐng)多多指教,虛心接受并學(xué)習(xí)获雕。
1. 如何獲取圖像的像素點(diǎn)
JavaScript
中獲取圖像的相關(guān)信息主要是靠Canvas
來獲取薄腻,借住api getImageData
來實(shí)現(xiàn)。
假設(shè)我們有這個(gè)一張圖片
PNG-32 452 * 452
的圖片届案,現(xiàn)在通過getImageData
拿到他上面的像素點(diǎn)信息
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// 假設(shè)我們的圖片路徑是
const source = 'http://xxx.com/test.png';
// 創(chuàng)建img對(duì)象
const img = new Image();
// 請(qǐng)求資源不需要憑證庵楷,如果服務(wù)器有做特殊限制,則這段代碼無效
img.crossOrigin = 'anonymous';
img.onload = () => {
canvas.width = img.width;
canvas.height = img.height;
// 將圖片繪制到canvas中楣颠,繪制起始點(diǎn)為x: 0, y: 0
ctx.drawImage(img, 0, 0);
// 獲取圖片像素點(diǎn)信息尽纽,從x: 0, y: 0開始,到圖片的寬度和高度
const imgData = ctx.getImageData(0, 0, img.width, img.height);
console.log(imgData);
}
img.src = source;
上面代碼打印出圖片像素點(diǎn)的數(shù)據(jù)
452 * 452 = 204304
球碉,總像素點(diǎn)有204304
蜓斧,那為什么是817216
呢,像素點(diǎn)集合是每四個(gè)為一個(gè)點(diǎn)睁冬,這四個(gè)分別代表r(0~255)挎春,g(0~255), b(0~255), a(0~255)
, 那么應(yīng)該在原有的基礎(chǔ)上204304 * 4 = 817216
這時(shí)的像素點(diǎn)就對(duì)了看疙。
2. 處理像素點(diǎn)
// imgData是第一步獲取到的像素點(diǎn)信息
const points = imgData.data;
const len = points.length;
// 定義每一行row
let row = 0;
// 定義存儲(chǔ)所有坐標(biāo)的集合
const imgPoints = [];
for (let i = 0; i < len; i += 4) {
//每4個(gè)的第0個(gè)代表r
const r = i;
// 每4個(gè)的第1個(gè)代表g
const g = i + 1;
// 每4個(gè)的第2個(gè)代表b
const b = i + 2;
// 每4個(gè)的第3個(gè)代表a
const a = i + 3;
// 把像素點(diǎn)轉(zhuǎn)換成坐標(biāo)點(diǎn)
/**
* 這里判斷是否為當(dāng)前行的信息,用y來做判斷
* 圖片寬度是452直奋,如果是第一行的話y為1能庆,第二行y為2...
* 用當(dāng)前遞增i的值除以4在除以圖片的寬度,向上取整脚线,計(jì)算出第幾行
*/
const y = Math.ceil(i / 4 / imgData.width);
// 如果y與當(dāng)前row不相等搁胆,也就是從下一行開始
if (y && y !== row) {
// 將當(dāng)前y賦值給row;
row = y;
// 將當(dāng)前行設(shè)置成集合
imgPoints[row] = [];
}
// 如果當(dāng)前集合存在
if (imgPoints[row]) {
// 如果透明度a的值存在
if (a) {
// 算出當(dāng)前這一行上每一個(gè)像素點(diǎn)的位置
const x = (i / 4) - (imgData.width * (y - 1));
// 將x, y,當(dāng)前這一行,數(shù)據(jù)結(jié)構(gòu)為map
imgPoints[row].push(
{
x,
y
}
);
} else {
// 如果當(dāng)前的透明值a不存在則存放0
imgPoints[row].push(0);
}
}
}
這樣我們的一個(gè)數(shù)據(jù)結(jié)構(gòu)已經(jīng)成功了
// 最終的數(shù)據(jù)結(jié)果邮绿,每一行都是一個(gè)集合渠旁,每一行集合里面有數(shù)據(jù)的則是map結(jié)構(gòu),沒有則是0
[
// 第一行的坐標(biāo)船逮, y為1
[
{
x: 1,
y: 1
},
{
x: 2,
y: 1,
},
// 當(dāng)前沒有坐標(biāo)點(diǎn)顾腊,是透明點(diǎn),記錄0
0
// ......
],
// 第二行的坐標(biāo)挖胃, y為2
[
{
x: 1,
y: 2
},
{
x: 2,
y: 2
},
// 當(dāng)前沒有坐標(biāo)點(diǎn)杂靶,是透明點(diǎn),記錄0
0
// ......
]
]
2. 獲取圖像邊緣
// 假設(shè)我們的數(shù)據(jù)結(jié)構(gòu)
[0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0]
// 0代表沒有坐標(biāo)點(diǎn)酱鸭,1代表我們的map集合
// 現(xiàn)在我們需要將其實(shí)和結(jié)尾連續(xù)的0清除
// 得到的結(jié)果
[1, 1, 1, 0, 0, 1, 1, 1]
// 現(xiàn)在繼續(xù)將數(shù)據(jù)結(jié)構(gòu)進(jìn)一步優(yōu)化吗垮,將第一個(gè)1, 到第一個(gè)0的位置但不包含0取出
[1, 1, 0, 0, 1, 1, 1]
// 繼續(xù)將后面的數(shù)據(jù)結(jié)構(gòu)一同取出
[1, 1, 0, 0, 1, 1]
// 最終將0過濾掉
[1, 1, 1, 1]
// 上面是我們需要的數(shù)據(jù)結(jié)構(gòu)
// 由此得出判斷判斷條件
const f = 數(shù)組的第0個(gè) === 1;
const l = 數(shù)組的最后一個(gè) === 1;
const conditionLeft = 當(dāng)前值 !== 0 && 當(dāng)前值的前一個(gè)值 === 0;
const conditionRight = 當(dāng)前值 !== 0 && 當(dāng)前值的后一個(gè)值 === 0;
// 這個(gè)判斷則過濾當(dāng)前這一行所有的數(shù)據(jù)凹髓,并不是y烁登,而是x
// 接下來過濾y
// 假設(shè)數(shù)據(jù)結(jié)構(gòu)
[
[0, 0, 1, 1, 0, 0],
[1 ,1, 0, 1, 1, 0]
]
// 此時(shí)我們應(yīng)該將所有列,注意:不是行扁誓,行是x防泵,現(xiàn)在我們要過濾列。
// 判斷條件和行的判斷條件基本一致
const f = 第1行 === 1;
const l = 最后1行 === 1;
const conditionTop = 當(dāng)前行的當(dāng)前值的上一行對(duì)應(yīng)的值 === 0 && 當(dāng)前行的當(dāng)前值 !== 0;
const conditionBottom = 當(dāng)前行的當(dāng)前值的下一個(gè)對(duì)應(yīng)的值 === 0 && 當(dāng)前行的當(dāng)前值 !== 0;
// 當(dāng)前判斷條件將過濾所有的y
接下來我們需要將過濾x和過濾y的條件蝗敢,篩選出來的數(shù)據(jù)存放在不同的數(shù)據(jù)結(jié)構(gòu)中
// 定義存放x的坐標(biāo)點(diǎn)
const xPoints = [];
// 定義存放y的坐標(biāo)點(diǎn)
const yPoints = [];
// imgPoints是存放所有的數(shù)據(jù)結(jié)構(gòu)捷泞,是個(gè)多維數(shù)組(二維數(shù)組)
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] && k !== 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);
}
})
})
// 上面的代碼就拿到了圖形邊緣的所有坐標(biāo)點(diǎn),不管這個(gè)圖形有多復(fù)雜寿谴,還是這個(gè)圖形分開的多個(gè)小圖形
假設(shè)我們上述不知道锁右,并且我們的程序中不知道這個(gè)圖形的顏色。推薦大家使用第三方的庫讶泰,可以轉(zhuǎn)換成邊緣圖像咏瑟。
本人推薦https://github.com/miguelmota/sobel,這個(gè)庫的代碼很少痪署,有興趣可以閱讀一下他里面的核心算法码泞。我還使用了一些其他的圖像轉(zhuǎn)邊緣圖像的其他庫,但出來的效果并不是很好狼犯。
3. 圖像吸附
當(dāng)你拿到邊緣點(diǎn)的時(shí)候余寥,你就可以對(duì)你的圖像進(jìn)行吸附的功能操作领铐。吸附功能還要根據(jù)各自的業(yè)務(wù)去實(shí)現(xiàn),在這里不在coding宋舷。
總結(jié)
寫到這里绪撵,有的人會(huì)說到,為什么不用一元二次方程祝蝠,或者點(diǎn)斜式方程去計(jì)算斜邊的點(diǎn)
音诈。
其實(shí)也是可以的,但這種情況僅限于固定程序绎狭。已知圖形的外邊緣的點(diǎn)细溅。比如一個(gè)三角形,已知3個(gè)點(diǎn)
坟岔,那么這個(gè)之后你可以通過方程式求出斜邊的坐標(biāo)點(diǎn)谒兄。但僅限于已知程序,如果一張圖像是通過程序?qū)肷绺叮蛘咄獠抠Y源引入的方式,那方程式計(jì)算出來要比直接取點(diǎn)要來的麻煩邻耕,麻煩的因素是多邊形和不規(guī)則圖形鸥咖,以及分散圖形。套用方程式會(huì)有額外的工作兄世。