摘要:前陣子因業務需求,需要對圖片進行一些特殊處理,例如反相,高亮,黑白等,都是使用來實現要實現上述所說的各種效果,最核心的事情便是對圖片的對象進行改動。后兩個代表的是圖片的寬高,不用多說。
前陣子因業務需求,需要對圖片進行一些特殊處理,例如反相,高亮,黑白等,都是使用Canvas來實現
ImageData要實現上述所說的各種效果,最核心的事情便是對圖片的ImageData對象進行改動。
ImageData對象是一個用來描述圖片屬性的一種數據對象,它有三個屬性,分別是data、width、height。后兩個代表的是圖片的寬高,不用多說。最重要的就是data屬性,它是一個Uint8ClampedArray(8位無符號整形固定數組)類型化數組。按照從上到下,從左到右的順序,它里面儲存了一張圖片的所有像素的rgba信息。
例如,一張圖片有4個像素,那data里面就有16個值,data[0]~data[3]的值就是第一個像素中的r、g、b、a值(不了解rgba的看這里)。
如何獲得一張圖片的ImageData對象?通過canvas的getImageData便可以很簡單地獲得:
const canvas = document.createElement("canvas") const ctx = canvas.getContext("2d") ctx.drawImage(img, 0, 0, canvas.width, canvas.height) const oriPeixel = ctx.getImageData(0, 0, canvas.width, canvas.height)
值得注意的是,ImageData里面的屬性都是只讀的,不能直接更改和賦值。
例如我們把上面的oriPeixel的屬性賦值,就會報以下的錯:
oriPeixel.data = [] > Uncaught TypeError: Cannot assign to read only property "data" of object "#"
了解了ImageData后,我們來看看效果demo
Demo 1:圖片反相漸變先看demo:demo-1
1、像素處理可以見到,圖片先是漸變成反相的樣子,再漸變為下一張圖片,是不是很酷炫。要現實這個,主要是用到getImageData及putImageData這兩個API
剛才我們說過,圖片的ImageData對象儲存著該圖片的每個像素的信息,想要得到圖片的反相效果,要作如下處理:
threshold (ctx, idx) { let pixels = ctx.getImageData(0, this.height * idx, this.width, this.height) let d = pixels.data for (let i = 0; i < d.length; i += 4) { let r = d[i] let g = d[i + 1] let b = d[i + 2] // 根據rgb求灰度值公式0.2126 * r + 0.7152 * g + 0.0722 * b let v = (0.2126 * r + 0.7152 * g + 0.0722 * b >= 100) ? 255 : 128 d[i] = d[i + 1] = d[i + 2] = v } return pixels }
返回的pixels便是圖片經過反相處理后的ImageData
這里主要是對每個像素的灰度值作過濾,大于等于100的,直接為白色,否則置于128
除此之外,還有黑白,高亮等其他像素處理,具體的可以看這篇文章
2、漸變處理有了經過反相處理后的圖片的ImageData數據,下一步要做的自然就是漸變賦值了。原生是沒有提供相關的API自動達成這種的漸變效果的,所以就需要我們自行實現一遍了,這個會比較麻煩。
用js寫過動畫的同學都知道,基本上都會使用requestAnimationFrame函數來進行幀處理,這里也不意外。
主要思路是這樣,圖片經過如下的順序進行漸變:
圖片1----->圖片1反相----->圖片2----->圖片2反相----->圖片3......
直接貼上主要代碼:
gradualChange () { // 圖片原始的ImageData數據 let oriPixels = this.ctx.getImageData(0, 0, this.width, this.height) let oriData = oriPixels.data // 圖片反相后的ImageData數據 let nextData = this.nextPixel[0].data let length = oriData.length let totalgap = 0 let gap = 0 let gapTemp for (let i = 0; i < length; i++) { // 計算每個rgba的差值,同時縮小處理。除的數值代表著漸變速度,越大越慢 gapTemp = (nextData[i] - oriData[i]) / 13 if (oriData[i] !== nextData[i]) { // 每個rgba值增量處理,簡單來說就是各種取整,[-1,1]區間直接取-1或1 gap = gapTemp > 1 ? Math.floor(gapTemp) : gapTemp < -1 ? Math.ceil(gapTemp) : oriData[i] < nextData[i] ? 1 : oriData[i] > nextData[i] ? -1 : 0 totalgap += Math.abs(gap) oriData[i] = oriData[i] + gap } } // 通過putImageData更新圖片 this.ctx.putImageData(oriPixels, 0, 0) // 總值為0,證明已經漸變完成 if (!totalgap) { this.nextPixel.shift() if (!this.nextPixel[0]) { this.isChange = false } } }
上面是漸變過程的主要代碼,完整的代碼可以查看:我是代碼
Demo 2:光條高亮移動效果同樣是先看demo
移動端:demo-2
PC端:demo-3
可以見到,移動端的demo中,光條上有幾個亮斑在同時移動;而PC端,則是在當鼠標hover上去之后,在光條中有一個圓形光斑的高亮效果,因為圖片本身是透明的,所以背景色做了深色處理。
1、像素處理需要說明的是,要實現這種效果,最好是找一些背景一部分透明,一部分帶有帶狀色條的圖片,例如我demo中的圖片。這類圖片有相當區域像素的rgba值為4個0,我們很容易對其做邊界處理
同樣的,實現這種效果也是需要對圖片像素的rgba值進行處理,但是會比圖片反相漸變復雜一些,因為這里需要先實現一個圓形的光斑。
光斑實現
既然是圓形光斑,肯定是先有圓心和半徑。在這里,我是在橫向的方向上,取光條的中心為圓心,半徑取50
實現的代碼在demo2的brightener函數里面,理解起來也不困難,給定一個y坐標,然后再遍歷一遍在這個y坐標下的像素,找出每條光條初始點和結束點的x坐標。rgba值連續兩點不為0的,就認為是仍處在光條中,還沒有達到邊界值
brightener (y) { // ....完整請看源代碼 for (let x = 0; x < cW; x++) { sPx = (cY * cW + x) * 4 if (oriData[sPx] || oriData[sPx + 1] || oriData[sPx + 2]) { startX || (startX = x) tempX = sPx + 4 if (oriData[tempX] || oriData[tempX + 1] || oriData[tempX + 2]) { continue } else { endX = tempX / 4 - cY * cW cX = Math.ceil((endX - startX) / 2) + startX startX = 0 res.push({ x: cX, y: cY }) } } } return res }
確定了圓心之后,就可以根據半徑確定一個圓,并用一個數組存儲這個圓內各個點,以便后續處理。過程也很簡單,就是初中學的那一套,兩點距離小于半徑就可以了
createArea (x, y, radius) { let result = [] for (let i = x - radius; i <= x + radius; i++) { for (let j = y - radius; j <= y + radius; j++) { let dx = i - x let dy = j - y if ((dx * dx + dy * dy) <= (radius * radius)) { let obj = {} if (i > 0 && j > 0) { obj.x = i obj.y = j result.push(obj) } } } } return result }
之后,就是實現一個光斑效果。在這里,我是從圓心向邊緣進行一個透明度的衰減漸變
// ... const validArr = this.createArea(x, y, radius) validArr.forEach((px, i) => { sPx = (px.y * cW + px.x) * 4 // 像素點的rgb值不全為0 if (oriData[sPx] || oriData[sPx + 1] || oriData[sPx + 2]) { distance = Math.sqrt((px.x - x) * (px.x - x) + (px.y - y) * (px.y - y)) // 根據距離和半徑的比率進行正比衰減 gap = Math.floor(opacity * (1 - distance / radius)) oriData[sPx + 3] += gap } }) // 更新ImageData this.ctx.putImageData(oriPixels, 0, 0)
到這里,一個光斑就這樣實現了
2、移動效果光斑有了,自然就是讓它動起來。這個就簡單啦,光斑生成的我們已經完成,那么我們只要把圓心動起來就可以了
在這里,同樣是使用requestAnimationFrame函數來進行幀處理。而光斑是從下向上移動的,可以看到startY在不斷遞減
autoPlay (timestamp) { if (this.startY <= -25) { let timeGap if (!this.progress) { this.progress = timestamp } timeGap = timestamp - this.progress // 判斷間隔時間是否滿足 if (timeGap > this.autoPlayInterval) { this.startY = this.height - 1 this.progress = 0 } } else { // 根據Y坐標生成圓心及光斑 const res = this.getBrightCenter(this.startY) this.brightnessCtx(res, 50, 60) this.startY -= 10 } window.requestAnimationFrame(this.autoPlay.bind(this), false) }
可以看到,無非就是循環startY坐標,生成新光斑的過程。而PC上的效果是當鼠標hover上去時有光斑效果,同理去掉這個自動移動的過程,對圖片的mousemove事件進行監聽,得出x,y坐標作為圓心即可
值得注意的是,因為在不斷地更新ImageData,所以我們需要一個臨時的canvas來存放原始圖片的ImageData數據。demo1也是作了同樣的處理
完整的代碼可以查看:PC端 、 移動端
總結以上便是使用Canvas實現一些圖片效果的介紹,權當拋磚引玉,各種看官也可以發揮想象力,實現自己的酷炫效果
參考ImageData
Uint8ClampedArray
Image Filters with Canvas
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/93446.html
摘要:暴露年齡了廣告詞飯后嚼兩粒大概,故事性很強,比較有意思同時直入主題,飯后吃益達口香糖有益健康。書里破繭成蝶說道,揣摩用戶的心思遠遠不夠,你不可能完整的想到別人在想什么,所以還需要去體驗用戶的生活。 用戶體驗(User Experience,簡稱UX 或是UE),它指用戶在使用一個產品、系統或者服務時建立起來的純主觀感受。 showImg(https://segmentfault.com...
摘要:暴露年齡了廣告詞飯后嚼兩粒大概,故事性很強,比較有意思同時直入主題,飯后吃益達口香糖有益健康。書里破繭成蝶說道,揣摩用戶的心思遠遠不夠,你不可能完整的想到別人在想什么,所以還需要去體驗用戶的生活。 用戶體驗(User Experience,簡稱UX 或是UE),它指用戶在使用一個產品、系統或者服務時建立起來的純主觀感受。 showImg(https://segmentfault.com...
閱讀 637·2021-11-22 15:32
閱讀 2725·2021-11-19 09:40
閱讀 2320·2021-11-17 09:33
閱讀 1277·2021-11-15 11:36
閱讀 1874·2021-10-11 10:59
閱讀 1485·2019-08-29 16:41
閱讀 1788·2019-08-29 13:45
閱讀 2156·2019-08-26 13:36