js操作像素实现图片编辑
基礎(chǔ)概念
眾所周知,圖片是由一個(gè)個(gè)像素點(diǎn)組成.每一個(gè)像素點(diǎn)包含四個(gè)值,決定了渲染出來(lái)的狀態(tài).這四個(gè)值為rgba(red, green, blue, alpha).
前三個(gè)值是紅綠藍(lán),值的大小范圍從0到255,或者從0%到100%之間.
第四個(gè)值alpha,規(guī)定了色彩的透明度,它的范圍為0到1之間.其中0代表完全透明,1代表完全可見(jiàn).
紅綠藍(lán)是色彩中的三元色,通過(guò)設(shè)置這三種顏色所占的比重,可以變幻出其他所有顏色.
比如某個(gè)標(biāo)簽的文字想設(shè)置為紅色,就可以通過(guò)css設(shè)置rgba值(代碼如下).
span {color: rgba(255, 0, 0, 1); }既然每個(gè)像素點(diǎn)可以通過(guò)rgba的值來(lái)表達(dá),那么一張圖片所包含的所有像素點(diǎn)都可以轉(zhuǎn)換成數(shù)據(jù).如果修改某部分像素點(diǎn)的rgba值,那該圖片渲染出來(lái)的效果就會(huì)發(fā)生變化,這樣便實(shí)現(xiàn)了圖片的編輯.
那怎么把圖片轉(zhuǎn)化成由像素點(diǎn)組成的數(shù)據(jù)呢?
實(shí)現(xiàn)
圖片轉(zhuǎn)換數(shù)據(jù)
一段簡(jiǎn)單的html結(jié)構(gòu)如下,頁(yè)面上放置一個(gè)原始圖片和一個(gè)canvas畫(huà)布,寬高都為300;
<body><p class="image"><img src="./img/rect.png" width="300" height="300" /></p><canvas id="myCanvas" width="300" height="300"></canvas> <body>首先編寫(xiě)一個(gè)getImageData函數(shù)將原始圖片轉(zhuǎn)化成數(shù)據(jù)(代碼如下).
圖片轉(zhuǎn)換成像素?cái)?shù)據(jù)按以下兩步操作.
- 調(diào)用ctx.drawImage將圖片繪制到畫(huà)布上
- 調(diào)用ctx.getImageData獲取像素?cái)?shù)據(jù)
最終的打印出來(lái)的數(shù)據(jù)結(jié)果(data)如下:
data = [255, 255, 255, 255, 255, 61, 61, 255, 255, 0, 0, 255, 255,...]data是一維數(shù)組,數(shù)組的前四個(gè)值[255, 255, 255, 255]為圖片第一個(gè)像素點(diǎn)的rgba值(ctx.getImageData返回的透明度大小范圍是從0 - 255的),[255, 61, 61, 255]
是圖片第二個(gè)像素點(diǎn)的rgba值,后面依次類(lèi)推.如此便成功的將圖片轉(zhuǎn)化成了數(shù)據(jù).
數(shù)據(jù)格式化
雖然圖片成功轉(zhuǎn)化成了數(shù)據(jù),但這樣的數(shù)據(jù)結(jié)構(gòu)很難操作,我們期待能夠?qū)?shù)據(jù)結(jié)構(gòu)的表現(xiàn)形式與圖片展示效果保持一致.
假如存在四個(gè)都是黑色的像素點(diǎn)(如下圖),總寬高都為2,值為[0, 0, 0, 255,0, 0, 0, 255,0, 0, 0, 255,0, 0, 0, 255].
通過(guò)某個(gè)函數(shù)轉(zhuǎn)換,數(shù)據(jù)就變成了下列格式.
[[[0, 0, 0, 255],[[0, 0, 0, 255]]], // 第一行[[0, 0, 0, 255],[[0, 0, 0, 255]]] // 第二行 ]上列數(shù)據(jù)格式和圖片的展示結(jié)構(gòu)保持了一致,可以很清晰的看出當(dāng)前圖形有多少行,每一行又有多少個(gè)像素點(diǎn),
以及每一個(gè)像素點(diǎn)的rgba值.
綜合上面描述,可以編寫(xiě)函數(shù)normalize(代碼如下)實(shí)現(xiàn)數(shù)據(jù)格式的轉(zhuǎn)換.
const dom = document.getElementById("myCanvas"); // canvas畫(huà)布getImageData(dom,"./img/rect.png").then((data)=>{console.log(normalize(data,dom.width,dom.height)); // 打印格式化后的像素?cái)?shù)據(jù) })function normalize(data,width,height){const list = [];const result = [];const len = Math.ceil(data.length/4);// 將每一個(gè)像素點(diǎn)的rgba四個(gè)值組合在一起for(i = 0;i<len;i++){const start = i*4;list.push([data[start],data[start+1],data[start+2],data[start+3]]);}//根據(jù)圖形的寬和高將數(shù)據(jù)進(jìn)行分類(lèi)for(hh = 0;hh < height;hh++){const tmp = [];for(ww = 0; ww < width;ww++){tmp.push(list[hh*width + ww]);}result.push(tmp);}return result; }換膚需求
通過(guò)normalize函數(shù)的轉(zhuǎn)換,一維數(shù)組的圖片數(shù)據(jù)轉(zhuǎn)換成了矩陣形式.有了矩陣,我們就可以更加方便的實(shí)現(xiàn)編輯圖片的需求.
首選我們簡(jiǎn)單實(shí)現(xiàn)一個(gè)圖片換膚的需求,將圖片中的黑色全部變成黃色(最終效果圖如下).
上方的原始圖片包含紅藍(lán)綠黑四種顏色,下方是換膚后生成的新圖片.
實(shí)現(xiàn)代碼如下,peeling函數(shù)負(fù)責(zé)變換圖片的顏色.
觀察代碼,由于黑色的rgb值是(0,0,0).那么只需要判斷出像素點(diǎn)是黑色,就重置其rgb值為(255,255,0)便能將圖片中所有的黑色換成黃色.
const dom = document.getElementById("myCanvas"); // canvas畫(huà)布getImageData(dom,"./img/rect.png").then((data)=>{data = peeling(data,dom.width,dom.height); // 換膚drawImage(dom,data); // 繪制圖像 })function peeling(data,w,h){data = normalize(data,w,h); // 轉(zhuǎn)化成多維數(shù)組// 將黑色變成黃色 (0,0,0) -> (255,255,0) for(let i = 0;i<data.length;i++){for(let j = 0;j<data[i].length;j++){//排除透明度的比較if(data[i][j].slice(0,3).join("") == "000"){data[i][j] = [255,255,0,data[i][j][3]];}}}return restoreData(data); // 轉(zhuǎn)化成一維數(shù)組 }矩陣的數(shù)據(jù)操作完了,還需要調(diào)用restoreData函數(shù)將多維數(shù)組再轉(zhuǎn)回一維數(shù)組傳給瀏覽器渲染.
function restoreData(data){const result = [];for(let i = 0;i<data.length;i++){for(let j = 0;j<data[i].length;j++){result.push(data[i][j][0],data[i][j][1],data[i][j][2],data[i][j][3]);}}return result;}渲染圖片
數(shù)據(jù)處理完畢后,還需將處理完的數(shù)據(jù)data傳遞給drawImage函數(shù)渲染成新圖片(代碼如下).
渲染圖像主要調(diào)用以下兩個(gè)api.
- ctx.createImageData.創(chuàng)建新的空白ImageData對(duì)象,通過(guò).data.set重新賦值.
- ctx.putImageData.將像素?cái)?shù)據(jù)繪制到畫(huà)布上.
至此新圖片便成功渲染了出來(lái),效果圖如下.
回顧上述操作,編輯圖像主要分解成以下三步.
- 將原始圖片轉(zhuǎn)化成矩陣數(shù)據(jù)(多維數(shù)組)
- 依據(jù)需求操作矩陣
- 將修改后的矩陣數(shù)據(jù)渲染成新圖片
上述第二步操作是圖像編輯的核心,很多復(fù)雜的變換效果可以通過(guò)編寫(xiě)矩陣算法實(shí)現(xiàn).
為了加深理解,利用上述知識(shí)點(diǎn)實(shí)現(xiàn)一個(gè)圖片旋轉(zhuǎn)的需求.
圖片旋轉(zhuǎn)
假定存在最簡(jiǎn)單的情況如下圖所示,其中左圖存在四個(gè)像素點(diǎn).第一行有兩個(gè)像素點(diǎn)1和2(這里用序號(hào)代替rgba值).
第二行也有兩個(gè)像素點(diǎn)3和4.數(shù)據(jù)源轉(zhuǎn)換成矩陣data后的值為 [[[1],[2]],[[3],[4]]].
如何將左圖按順時(shí)針旋轉(zhuǎn)90度變成右圖?
通過(guò)觀察圖中位置關(guān)系,只需要將data中的數(shù)據(jù)做位置變換,讓data = [[[1],[2]],[[3],[4]]]變成data = [[[3],[1]],[[4],[2]]],就可以實(shí)現(xiàn)圖片變換.
四個(gè)像素點(diǎn)可以直接用索引交換數(shù)組的值,但一張圖片動(dòng)輒幾十萬(wàn)個(gè)像素,那該如何進(jìn)行操作?
這種情況下通常需要編寫(xiě)一個(gè)基礎(chǔ)算法來(lái)實(shí)現(xiàn)圖片的旋轉(zhuǎn).
首先從下圖中尋找規(guī)律,圖中有左 - 中 - 右三種圖片狀態(tài),為了從左圖的1-2-3-4變成右圖的3-1-4-2,可以通過(guò)以下兩步實(shí)現(xiàn).
-
尋找矩陣的高度的中心軸線,上下兩側(cè)按照軸線進(jìn)行數(shù)據(jù)交換.比如左圖1 - 2和3 - 4之間可以畫(huà)一條軸線,上下兩側(cè)圍繞軸線交換數(shù)據(jù),第一行變成了3 - 4,第二行變成了1 - 2.通過(guò)第一步操作變成了中圖的樣子.
-
中圖的對(duì)角線3 - 2和右圖一致,剩下的將對(duì)角線兩側(cè)的數(shù)據(jù)對(duì)稱(chēng)交換就可以變成右圖.比如將中圖的1和4進(jìn)行值交換.操作完后便實(shí)現(xiàn)了圖片的旋轉(zhuǎn).值得注意的是4的數(shù)組索引是[0][1],而1的索引是[1][0],剛好索引順序顛倒.
通過(guò)以上描述規(guī)律便可編寫(xiě)下面函數(shù)實(shí)現(xiàn)圖片的旋轉(zhuǎn).
const dom = document.getElementById("myCanvas"); // canvas畫(huà)布// getImageData 獲取像素?cái)?shù)據(jù) getImageData(dom,"./img/rect.png").then((data)=>{data = rotate90(data,dom.width,dom.height); // 順時(shí)針旋轉(zhuǎn)90度drawImage(dom,data); // 繪制圖像 })function rotate90(data,w,h){data = normalize(data,w,h); // 轉(zhuǎn)化成矩陣// 圍繞中間行上下顛倒const mid = h/2; // 找出中間行for(hh = 0;hh < mid;hh++){const symmetric_hh = h - hh -1; // 對(duì)稱(chēng)行的索引for(ww = 0;ww<w;ww++){let tmp = data[hh][ww];data[hh][ww] = data[symmetric_hh][ww];data[symmetric_hh][ww] = tmp;}}// 根據(jù)對(duì)角線進(jìn)行值交換for(hh = 0;hh < h;hh++){for(ww = hh+1;ww<w;ww++){let tmp = data[hh][ww];data[hh][ww] = data[ww][hh];data[ww][hh] = tmp;}}return restoreData(data); // 轉(zhuǎn)化成一維數(shù)組 }由于我們定義的canvas寬高都為300,上面的旋轉(zhuǎn)算法只適用于正方形(長(zhǎng)方形的圖片要另外編寫(xiě)).
最終頁(yè)面展示的效果圖如下(上面是原始圖片,下面是畫(huà)布生成的新圖片).
總結(jié)
以上是生活随笔為你收集整理的js操作像素实现图片编辑的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Audified U73b 复古压缩器评
- 下一篇: 王者荣耀服务器维护到什么时候7月9号,王