濮阳杆衣贸易有限公司

主頁(yè) > 知識(shí)庫(kù) > html5利用canvas實(shí)現(xiàn)顏色容差摳圖功能

html5利用canvas實(shí)現(xiàn)顏色容差摳圖功能

熱門標(biāo)簽:惡搞電話機(jī)器人 黃石ai電銷機(jī)器人呼叫中心 如何查看地圖標(biāo)注 欣鼎電銷機(jī)器人 效果 電話機(jī)器人技術(shù) 高德地圖標(biāo)注商戶怎么標(biāo) 智能電銷機(jī)器人被禁用了么 地圖標(biāo)注軟件打印出來(lái) ok電銷機(jī)器人

利用canvas的getImageData,我們可以獲取到一張圖片每一個(gè)像素的信息,而通過(guò)對(duì)每一個(gè)像素信息的對(duì)比,我們就可以找到需要消去的像素點(diǎn)。比如下面這一張圖片,如果我們想要扣去白色部分(粉色是body的背景顏色)。

 

let canvas = document.querySelector('#canvas');
let context = canvas.getContext('2d');
let img = document.createElement('img');
img.src = './head2.png';
img.onload = function () {
    canvas.height = img.height;
    canvas.width = img.width;
    context.drawImage(img, 0, 0);
    cutout(canvas, [255, 255, 255], 0.2); // 對(duì)白色進(jìn)行摳除,容差為0.2
}
function cutout(canvas, color, range = 0) {
    let context = canvas.getContext('2d');
    let imageInfo = context.getImageData(0, 0, canvas.width, canvas.height);
    // pixiArr是一個(gè)數(shù)組,每四個(gè)數(shù)組元素代表一個(gè)像素點(diǎn),這四個(gè)數(shù)組元素分別對(duì)應(yīng)一個(gè)像素的r,g,b,a值。
    let pixiArr = imageInfo.data;
    for (let i = 0; i < pixiArr.length; i += 4) {
    // 匹配到目標(biāo)像素就將目標(biāo)像素的alpha設(shè)為0
        if (testColor([pixiArr[i], pixiArr[i + 1], pixiArr[i + 2]], color, range)) pixiArr[i + 3] = 0;
    }
    context.putImageData(imageInfo, 0, 0);
}
function testColor(current, target, range) {
    for (let i = 0; i < 3; i++) {
        if (!((1 - range) * target[i] <= current[i] && (1 + range) * target[i] >= current[i])) return false;
    }
    return true;
}

 

testColor(current, target, range) 方法三個(gè)參數(shù)分別為 待檢測(cè)像素點(diǎn)的rgb數(shù)組 、 目標(biāo)像素點(diǎn)的rgb數(shù)組 和 容差范圍 ,這里的容差只是簡(jiǎn)單用r、g、b的值分別乘以(1 + range)和(1 - range)來(lái)計(jì)算并對(duì)比,不同的容差參數(shù)會(huì)得到不同的效果↓

range = 0.095

 

range = 0.1

 

range = 0.2

 

當(dāng)然對(duì)于底色是標(biāo)準(zhǔn)的純色的圖片就不需要容差了。

邊界處理

但是有時(shí)候我們希望有一個(gè)邊界,讓摳圖操作不對(duì)邊界內(nèi)部的像素造成影響。比如上面的圖片,我們希望不會(huì)對(duì)人物頭像內(nèi)部的像素造成影響。 如果我們一行一行來(lái)看,是不是只要在碰到不是邊界像素的時(shí)候停止操作,就可以達(dá)到效果了呢?

我們對(duì)每一行分別進(jìn)行掃描,定義一個(gè)左指針 left 指向這一行的第一個(gè)像素,定義一個(gè)右指針 right 指向這一行的最后一個(gè)像素,并用一個(gè) leftF 標(biāo)識(shí)左邊是否碰到邊界,一個(gè) rightF 標(biāo)識(shí)右邊是否碰到邊界,當(dāng)沒(méi)碰到邊界時(shí)指針就一直向內(nèi)收縮,直到兩個(gè)指針都碰到邊界或者左右指針重合就跳到下一行,直到所有行都掃描完畢。

function cutout(canvas, color, range = 0) {
    let context = canvas.getContext('2d');
    let imageInfo = context.getImageData(0, 0, canvas.width, canvas.height);
    let pixiArr = imageInfo.data;
    for (let row = 0; row < canvas.height; row++) {
        let left = row * 4 * canvas.width; // 指向行首像素
        let right = left + 4 * canvas.width - 1 - 3; // 指向行尾像素
        let leftF = false; // 左指針是否碰到邊界的標(biāo)識(shí)
        let rightF = false; // 右指針是否碰到邊界的標(biāo)識(shí)
        while (!leftF || !rightF) { // 當(dāng)左右指針都為true,即都碰到邊界時(shí)結(jié)束
            if (!leftF) {
                if (testColor([pixiArr[left], pixiArr[left + 1], pixiArr[left + 2]], color, range)) {
                    pixiArr[left + 3] = 0; // 此像素的alpha設(shè)為0
                    left += 4; // 移到下一個(gè)像素
                } else leftF = true; // 碰到邊界
            }
            if (!rightF) {
                if (testColor([pixiArr[right], pixiArr[right + 1], pixiArr[right + 2]], color, range)) {
                    pixiArr[right + 3] = 0;
                    right -= 4;
                } else rightF = true;
            }
            if (left == right) { // 左右指針重合
                leftF = true;
                rightF = true;
            };
        }
    }
    context.putImageData(imageInfo, 0, 0);
}

 

雖然大概完成了我們的需求,但是看一下上面頭發(fā)那為啥會(huì)多了一塊白色

 

因?yàn)槲覀儍H僅只進(jìn)行了行掃描,當(dāng)左指針碰到頭發(fā)時(shí)就會(huì)停止掃描,但是頭發(fā)弧度里面的就無(wú)法被掃描到了,我們還需要進(jìn)行列掃描,改造一下上面的方法:

function cutout(canvas, color, range = 0) {
    let context = canvas.getContext('2d');
    let imageInfo = context.getImageData(0, 0, canvas.width, canvas.height);
    let pixiArr = imageInfo.data;
    for (let row = 0; row < canvas.height; row++) {
        let left = row * 4 * canvas.width;
        let right = left + 4 * canvas.width - 1 - 3;
        let leftF = false;
        let rightF = false;
        while (!leftF || !rightF) {
            if (!leftF) {
                if (testColor([pixiArr[left], pixiArr[left + 1], pixiArr[left + 2]], color, range)) {
                    pixiArr[left + 3] = 0;
                    left += 4;
                } else leftF = true;
            }
            if (!rightF) {
                if (testColor([pixiArr[right], pixiArr[right + 1], pixiArr[right + 2]], color, range)) {
                    pixiArr[right + 3] = 0;
                    right -= 4;
                } else rightF = true;
            }
            if (left == right) {
                leftF = true;
                rightF = true;
            };
        }
    }
    // 同理進(jìn)行列掃描
    for (let col = 0; col < canvas.width; col++) {
        let top = col * 4; // 指向列頭
        let bottom = top + (canvas.height - 2) * canvas.width * 4 + canvas.width * 4; // 指向列尾
        let topF = false;
        let bottomF = false;
        while (!topF || !bottomF) {
            if (!topF) {
                if (testColor([pixiArr[top], pixiArr[top + 1], pixiArr[top + 2]], color, range)) {
                    pixiArr[top + 3] = 0;
                    top += canvas.width * 4;
                } else topF = true;
            }
            if (!bottomF) {
                if (testColor([pixiArr[bottom], pixiArr[bottom + 1], pixiArr[bottom + 2]], color, range)) {
                    pixiArr[bottom + 3] = 0;
                    bottom -= canvas.width * 4;
                } else bottomF = true;
            }

            if (top == bottom) {
                topF = true;
                bottomF = true;
            };
        }
    }

    context.putImageData(imageInfo, 0, 0);
}

 

至于top和bottom為啥是那樣計(jì)算畫個(gè)矩陣圖大概就知道了。

 

處理后↓

 

其實(shí)還可以先將 pixiArr 包裝為以一個(gè)像素點(diǎn)為單位的矩陣

[
    [{r: 255, g: 255, b: 255, a: 1}, {r: 255, g: 255, b: 255, a: 1}, {r: 255, g: 255, b: 255, a: 1}],
    [{r: 255, g: 255, b: 255, a: 1}, {r: 255, g: 255, b: 255, a: 1}, {r: 255, g: 255, b: 255, a: 1}]
    [{r: 255, g: 255, b: 255, a: 1}, {r: 255, g: 255, b: 255, a: 1}, {r: 255, g: 255, b: 255, a: 1}]
]

處理后計(jì)算像素下標(biāo)也就會(huì)更簡(jiǎn)單,列掃描時(shí)直接先將這個(gè)矩陣旋轉(zhuǎn),再用行掃描處理一遍就行了。這樣處理pixiArr也有利于進(jìn)一步對(duì)算法進(jìn)行優(yōu)化。

上述方法雖然大概完成了摳圖效果,但是這種簡(jiǎn)單處理還會(huì)有許多情況沒(méi)有考慮到。

比如右邊頭發(fā)這里是行掃描和列掃描都無(wú)法觸碰到的區(qū)域↓

 

下面的衣服也因?yàn)轭伾偷咨粯忧覜](méi)有邊界在列掃描中被直接抹去了↓

 

最后

對(duì)于主體和底色區(qū)分度很大的圖片來(lái)說(shuō),最開始的那種方法就已經(jīng)夠用了。這篇中我采用的容差和邊界處理算法的優(yōu)化空間還很大,但是它們是非常容易實(shí)現(xiàn)與理解的,這篇權(quán)當(dāng)做一個(gè)引子,各位完全可以根據(jù)自己的能力繼續(xù)去實(shí)現(xiàn)邊界與容差算法。

總結(jié)

以上所述是小編給大家介紹的html5利用canvas實(shí)現(xiàn)顏色容差摳圖功能,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
如果你覺得本文對(duì)你有幫助,歡迎轉(zhuǎn)載,煩請(qǐng)注明出處,謝謝!

標(biāo)簽:萍鄉(xiāng) 綏化 盤錦 阿壩 聊城 中山 赤峰 金昌

巨人網(wǎng)絡(luò)通訊聲明:本文標(biāo)題《html5利用canvas實(shí)現(xiàn)顏色容差摳圖功能》,本文關(guān)鍵詞  html5,利用,canvas,實(shí)現(xiàn),顏色,;如發(fā)現(xiàn)本文內(nèi)容存在版權(quán)問(wèn)題,煩請(qǐng)?zhí)峁┫嚓P(guān)信息告之我們,我們將及時(shí)溝通與處理。本站內(nèi)容系統(tǒng)采集于網(wǎng)絡(luò),涉及言論、版權(quán)與本站無(wú)關(guān)。
  • 相關(guān)文章
  • 下面列出與本文章《html5利用canvas實(shí)現(xiàn)顏色容差摳圖功能》相關(guān)的同類信息!
  • 本頁(yè)收集關(guān)于html5利用canvas實(shí)現(xiàn)顏色容差摳圖功能的相關(guān)信息資訊供網(wǎng)民參考!
  • 推薦文章
    北海市| 隆林| 凭祥市| 灵武市| 乌苏市| 大理市| 茌平县| 苗栗县| 罗平县| 山阴县| 若尔盖县| 大理市| 克东县| 商洛市| 岢岚县| 安徽省| 留坝县| 高淳县| 阳江市| 富民县| 兴海县| 东城区| 加查县| 德令哈市| 都兰县| 尼玛县| 林州市| 卓资县| 绥滨县| 米泉市| 颍上县| 轮台县| 池州市| 吉隆县| 庆元县| 天镇县| 崇阳县| 民勤县| 潢川县| 盐源县| 彭阳县|