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