国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

玩轉(zhuǎn)你的圖片,各種圖片效果的Canvas實(shí)現(xiàn)

alexnevsky / 3565人閱讀

摘要:前陣子因業(yè)務(wù)需求,需要對(duì)圖片進(jìn)行一些特殊處理,例如反相,高亮,黑白等,都是使用來實(shí)現(xiàn)要實(shí)現(xiàn)上述所說的各種效果,最核心的事情便是對(duì)圖片的對(duì)象進(jìn)行改動(dòng)。后兩個(gè)代表的是圖片的寬高,不用多說。

前陣子因業(yè)務(wù)需求,需要對(duì)圖片進(jìn)行一些特殊處理,例如反相,高亮,黑白等,都是使用Canvas來實(shí)現(xiàn)

ImageData

要實(shí)現(xiàn)上述所說的各種效果,最核心的事情便是對(duì)圖片的ImageData對(duì)象進(jìn)行改動(dòng)。

ImageData對(duì)象是一個(gè)用來描述圖片屬性的一種數(shù)據(jù)對(duì)象,它有三個(gè)屬性,分別是datawidthheight。后兩個(gè)代表的是圖片的寬高,不用多說。最重要的就是data屬性,它是一個(gè)Uint8ClampedArray(8位無符號(hào)整形固定數(shù)組)類型化數(shù)組。按照從上到下,從左到右的順序,它里面儲(chǔ)存了一張圖片的所有像素的rgba信息。

例如,一張圖片有4個(gè)像素,那data里面就有16個(gè)值,data[0]~data[3]的值就是第一個(gè)像素中的r、g、b、a值(不了解rgba的看這里)。

如何獲得一張圖片的ImageData對(duì)象?通過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的屬性賦值,就會(huì)報(bào)以下的錯(cuò):

oriPeixel.data = []

> Uncaught TypeError: Cannot assign to read only property "data" of object "#"

了解了ImageData后,我們來看看效果demo

Demo 1:圖片反相漸變

先看demo:demo-1

1、像素處理

可以見到,圖片先是漸變成反相的樣子,再漸變?yōu)橄乱粡垐D片,是不是很酷炫。要現(xiàn)實(shí)這個(gè),主要是用到getImageDataputImageData這兩個(gè)API

剛才我們說過,圖片的ImageData對(duì)象儲(chǔ)存著該圖片的每個(gè)像素的信息,想要得到圖片的反相效果,要作如下處理:

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]
    // 根據(jù)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便是圖片經(jīng)過反相處理后的ImageData

這里主要是對(duì)每個(gè)像素的灰度值作過濾,大于等于100的,直接為白色,否則置于128

除此之外,還有黑白,高亮等其他像素處理,具體的可以看這篇文章

2、漸變處理

有了經(jīng)過反相處理后的圖片的ImageData數(shù)據(jù),下一步要做的自然就是漸變賦值了。原生是沒有提供相關(guān)的API自動(dòng)達(dá)成這種的漸變效果的,所以就需要我們自行實(shí)現(xiàn)一遍了,這個(gè)會(huì)比較麻煩。

用js寫過動(dòng)畫的同學(xué)都知道,基本上都會(huì)使用requestAnimationFrame函數(shù)來進(jìn)行幀處理,這里也不意外。

主要思路是這樣,圖片經(jīng)過如下的順序進(jìn)行漸變:

圖片1----->圖片1反相----->圖片2----->圖片2反相----->圖片3......

直接貼上主要代碼:

gradualChange () {
  // 圖片原始的ImageData數(shù)據(jù)
  let oriPixels = this.ctx.getImageData(0, 0, this.width, this.height)
  let oriData = oriPixels.data
  // 圖片反相后的ImageData數(shù)據(jù)
  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++) {
    // 計(jì)算每個(gè)rgba的差值,同時(shí)縮小處理。除的數(shù)值代表著漸變速度,越大越慢
    gapTemp = (nextData[i] - oriData[i]) / 13

    if (oriData[i] !== nextData[i]) {
      // 每個(gè)rgba值增量處理,簡單來說就是各種取整,[-1,1]區(qū)間直接取-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,證明已經(jīng)漸變完成
  if (!totalgap) {
    this.nextPixel.shift()
    if (!this.nextPixel[0]) {
      this.isChange = false
    }
  }
}

上面是漸變過程的主要代碼,完整的代碼可以查看:我是代碼

Demo 2:光條高亮移動(dòng)效果

同樣是先看demo

移動(dòng)端:demo-2

PC端:demo-3

可以見到,移動(dòng)端的demo中,光條上有幾個(gè)亮斑在同時(shí)移動(dòng);而PC端,則是在當(dāng)鼠標(biāo)hover上去之后,在光條中有一個(gè)圓形光斑的高亮效果,因?yàn)閳D片本身是透明的,所以背景色做了深色處理。

1、像素處理

需要說明的是,要實(shí)現(xiàn)這種效果,最好是找一些背景一部分透明,一部分帶有帶狀色條的圖片,例如我demo中的圖片。這類圖片有相當(dāng)區(qū)域像素的rgba值為4個(gè)0,我們很容易對(duì)其做邊界處理

同樣的,實(shí)現(xiàn)這種效果也是需要對(duì)圖片像素的rgba值進(jìn)行處理,但是會(huì)比圖片反相漸變復(fù)雜一些,因?yàn)檫@里需要先實(shí)現(xiàn)一個(gè)圓形的光斑。

光斑實(shí)現(xiàn)

既然是圓形光斑,肯定是先有圓心和半徑。在這里,我是在橫向的方向上,取光條的中心為圓心,半徑取50

實(shí)現(xiàn)的代碼在demo2的brightener函數(shù)里面,理解起來也不困難,給定一個(gè)y坐標(biāo),然后再遍歷一遍在這個(gè)y坐標(biāo)下的像素,找出每條光條初始點(diǎn)和結(jié)束點(diǎn)的x坐標(biāo)。rgba值連續(xù)兩點(diǎn)不為0的,就認(rèn)為是仍處在光條中,還沒有達(dá)到邊界值

brightener (y) {
  // ....完整請(qǐng)看源代碼
  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
}

確定了圓心之后,就可以根據(jù)半徑確定一個(gè)圓,并用一個(gè)數(shù)組存儲(chǔ)這個(gè)圓內(nèi)各個(gè)點(diǎn),以便后續(xù)處理。過程也很簡單,就是初中學(xué)的那一套,兩點(diǎn)距離小于半徑就可以了

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
}

之后,就是實(shí)現(xiàn)一個(gè)光斑效果。在這里,我是從圓心向邊緣進(jìn)行一個(gè)透明度的衰減漸變

// ...
const validArr = this.createArea(x, y, radius)
validArr.forEach((px, i) => {
  sPx = (px.y * cW + px.x) * 4
  // 像素點(diǎn)的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))
    // 根據(jù)距離和半徑的比率進(jìn)行正比衰減
    gap = Math.floor(opacity * (1 - distance / radius))
    oriData[sPx + 3] += gap
  }
})
// 更新ImageData
this.ctx.putImageData(oriPixels, 0, 0)

到這里,一個(gè)光斑就這樣實(shí)現(xiàn)了

2、移動(dòng)效果

光斑有了,自然就是讓它動(dòng)起來。這個(gè)就簡單啦,光斑生成的我們已經(jīng)完成,那么我們只要把圓心動(dòng)起來就可以了

在這里,同樣是使用requestAnimationFrame函數(shù)來進(jìn)行幀處理。而光斑是從下向上移動(dòng)的,可以看到startY在不斷遞減

autoPlay (timestamp) {
  if (this.startY <= -25) {
    let timeGap
    if (!this.progress) {
      this.progress = timestamp
    }
    timeGap = timestamp - this.progress
    // 判斷間隔時(shí)間是否滿足
    if (timeGap > this.autoPlayInterval) {
      this.startY = this.height - 1
      this.progress = 0
    }
  } else {
    // 根據(jù)Y坐標(biāo)生成圓心及光斑 
    const res = this.getBrightCenter(this.startY)
    this.brightnessCtx(res, 50, 60)
    this.startY -= 10
  }
  window.requestAnimationFrame(this.autoPlay.bind(this), false)
}

可以看到,無非就是循環(huán)startY坐標(biāo),生成新光斑的過程。而PC上的效果是當(dāng)鼠標(biāo)hover上去時(shí)有光斑效果,同理去掉這個(gè)自動(dòng)移動(dòng)的過程,對(duì)圖片的mousemove事件進(jìn)行監(jiān)聽,得出xy坐標(biāo)作為圓心即可

值得注意的是,因?yàn)樵诓粩嗟馗?b>ImageData,所以我們需要一個(gè)臨時(shí)的canvas來存放原始圖片的ImageData數(shù)據(jù)。demo1也是作了同樣的處理

完整的代碼可以查看:PC端 、 移動(dòng)端

總結(jié)

以上便是使用Canvas實(shí)現(xiàn)一些圖片效果的介紹,權(quán)當(dāng)拋磚引玉,各種看官也可以發(fā)揮想象力,實(shí)現(xiàn)自己的酷炫效果

參考

ImageData

Uint8ClampedArray

Image Filters with Canvas

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/93446.html

相關(guān)文章

  • 【用戶體驗(yàn)優(yōu)化方案】到了,請(qǐng)簽收~

    摘要:暴露年齡了廣告詞飯后嚼兩粒大概,故事性很強(qiáng),比較有意思同時(shí)直入主題,飯后吃益達(dá)口香糖有益健康。書里破繭成蝶說道,揣摩用戶的心思遠(yuǎn)遠(yuǎn)不夠,你不可能完整的想到別人在想什么,所以還需要去體驗(yàn)用戶的生活。 用戶體驗(yàn)(User Experience,簡稱UX 或是UE),它指用戶在使用一個(gè)產(chǎn)品、系統(tǒng)或者服務(wù)時(shí)建立起來的純主觀感受。 showImg(https://segmentfault.com...

    cheng10 評(píng)論0 收藏0
  • 【用戶體驗(yàn)優(yōu)化方案】到了,請(qǐng)簽收~

    摘要:暴露年齡了廣告詞飯后嚼兩粒大概,故事性很強(qiáng),比較有意思同時(shí)直入主題,飯后吃益達(dá)口香糖有益健康。書里破繭成蝶說道,揣摩用戶的心思遠(yuǎn)遠(yuǎn)不夠,你不可能完整的想到別人在想什么,所以還需要去體驗(yàn)用戶的生活。 用戶體驗(yàn)(User Experience,簡稱UX 或是UE),它指用戶在使用一個(gè)產(chǎn)品、系統(tǒng)或者服務(wù)時(shí)建立起來的純主觀感受。 showImg(https://segmentfault.com...

    eternalshallow 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<