canvas-玩轉每個像素-濾鏡

原文請見個人github博客>>html

前言

接觸canvas應該是在去年半次元作製品計劃吧,想一想也很久了,不過,那會兒天天累得和狗同樣,週末還要上課。經驗總結也基本都記錄在OneNote,思惟跳躍性的記錄也就不適合做爲博客發佈。
如今來了新公司,一段時間忙,一段時間閒成狗,因此就會再從新總結,寫寫博客。c++

canvas簡介

引用自MDN:
<canvas> 是 HTML5 新增的元素,可用於經過使用JavaScript中的腳原本繪製圖形。例如,它能夠用於繪製圖形,製做照片,建立動畫,甚至能夠進行實時視頻處理或渲染。git

我認爲canvas最好的教程就是MDN的,canvas基礎補充請戳這裏>>github

canvas繪製圖片——drawImage

咱們能夠將已經加載好的圖片畫到canvas上。
繪製圖片的api接口:
drawImage(image, x, y, width, height)
其中 image 是 image 或者 canvas 對象,x 和 y 是其在目標 canvas 裏的起始座標。width 和 height,這兩個參數用來控制 當像canvas畫入時應該縮放的大小。
drawImage其實還有四個額外參數,通常用來作截圖,這裏由於文章不涉及就不贅述了。有興趣的小夥伴能夠戳這裏>>canvas

獲取canvas全部的像素點——getImageData

getImageData是canvas提供的一個很是強大的接口,它能夠獲取canvas的全部的像素點的值。不過,值的展示形式和通常的rgba或rgb等屬性不一樣,全部的值會被記錄在一個Uint8ClampedArray的一維數組裏面。api

知識補充——Uint8ClampedArray

The Uint8ClampedArray typed array represents an array of 8-bit unsigned integers clamped to 0-255; if you specified a value that is out of the range of [0,255], 0 or 255 will be set instead; if you specify a non-integer, the nearest integer will be set. The contents are initialized to 0.數組

翻譯一下:dom

Uint8ClampedArray類型數組表示一個8-bit無符號整數,即0-255區間;若是你設了一個的值超出了[0, 255]的範圍,他們會被0或者255代替(小於0代替爲0,大於255替代爲255);若是你設了一個非整數,會被替代爲這個小數最接近的整數。全部的初始值爲0;ide

那麼問題來了,數組是怎麼存每一個像素點的rgba值的呢?

見圖:
圖片描述
若是,canvas將每一個像素點的值按照rgba這樣的順序一個一個的存入Unit8ClampedArray裏面。
所以,數組的長度爲length = canvas.width canvas.height 4。動畫

知道了這種關係,咱們不妨把這個一維數組想象成二維數組,想象它是一個平面圖,如圖:
圖片描述
一個格子表明一個像素
w = 圖像寬度
h = 圖像高度

這樣,咱們能夠很容易獲得點(x, y)在一維數組中對應的位置。咱們想想,點(1, 1)座標對應的是數組下標爲0,點(2, 1)對應的是數組下標4,假設圖像寬度爲2*2,那麼點(1,2)對應下標就是index=((2 - 1)*w + (1 - 1))*4 = 8
推導出公式:index = [(y - 1) w + (x - 1) h] * 4

知識補充

咱們既然已經可以拿到圖像的每個像素點,那麼咱們就能夠隨心所欲啦!
圖片描述
不過客官別急,咱們還有點小知識要補充,避免代碼實現的過程陷入迷茫~

知識補充——createImageData

The CanvasRenderingContext2.createImageData() method of the Canvas 2D API creates a new, blank ImageData object with the specified dimensions. All of the pixels in the new object are transparent black.

翻譯(非直譯):

createImageData是在canvas在取渲染上下文爲2D(即canvas.getContext('2d'))的時候提供的接口。做用是建立一個新的、空的、特定尺寸的ImageData對象。其中全部的像素點初始都爲黑色透明。

咱們會用到ctx.createImageData(width, height)這個接口,width和height是新ImageData對象的初始長寬。

ImageData又是啥?

ImageData是一個對象,其實咱們在canvas.getImageData拿到的對象就是ImageData,它內部由width,height,Uint8ClampedArray組成,
如:{data: Uint8ClampedArray(958400), width: 400, height: 599}

知識補充——createImageData

The CanvasRenderingContext2D.putImageData() method of the Canvas 2D API paints data from the given ImageData object onto the bitmap. If a dirty rectangle is provided, only the pixels from that rectangle are painted. This method is not affected by the canvas transformation matrix.

翻譯:

CanvasRenderingContext2D.putImageData() 方法做爲canvas 2D API 以給定的ImageData對象繪製數據進位圖。若是提供了髒矩形,將只有矩形的像素會被繪製。這個方法不會影響canvas的形變矩陣。

看上去有點迷糊,矩陣都出來了。不過不用擔憂,咱們只關注第一句就好,忽略「若是「以後的文字。
咱們將會用到ctx.putImageData(imagedata, dx, dy)接口,imageData就是用戶提供的ImageData對象,dx和dy分別是canvas座標系的x點和y點,將從這個(dx,dy)開始輸入數據。

實現濾鏡

終於迎來了最後的階段!
直接上代碼:
html:

<div class="box">
    <img id="img" src="index.png">
</div>
<canvas id="canvas"></canvas>

js

draw()

function draw() {
  const canvas = document.getElementById('canvas')
  const ctx = canvas.getContext('2d')
  const img = document.getElementById('img')
  // 等圖片加載完之後才能獲取圖片信息
  img.onload = function() {
    const style = window.getComputedStyle(img)
    const w = style.width
    const h = style.height
    const ws = w.replace(/px/, '')
    const hs = h.replace(/px/, '')
    canvas.width = ws
    canvas.height = hs
    ctx.drawImage(img, 0, 0, canvas.width, canvas.height)
    // 修改顏色準備
    const originColor = ctx.getImageData(0, 0, ws, hs)
    // 保存ImageData裏的Uint8ClampedArray數據
    const originColorData = originColor.data
    // 建立一個空的圖像,這時canvas裏其實已經沒原來的圖像了
    const output = ctx.createImageData(ws, hs) 
    const outputData = output.data
    // 詭異畫風按鈕綁定
    const weirdBtn = document.getElementById('weird')
    weirdBtn.addEventListener('click', function() {
      // 詭異畫風數據處理(咱們能夠用各類處理方法處理圖像數據,達到想要的效果)
      weird(originColorData, outputData, ws, hs)
      ctx.putImageData(output, 0, 0)
    })
  }
}

// 詭異
function weird(originColorData, outputData, ws, hs) {
  let random
  let randomData
  let index;
  let r, g, b;
  // 逐行掃描
  for (let y = 1; y <= hs; y++) {
    // 逐列掃描
    for (let x = 1; x <= ws; x++) {
      // rgb處理
      for (let c = 0; c < 3; c++) {
        random = Math.random(0, 255) * 100
        randomData = Math.abs(random - originColorData[index])
        index = ((y-1) * ws + (x-1)) * 4 + c
        outputData[index] = randomData
      }
      // alpha處理,咱們就讓透明度一直未1就行了
      outputData[index + 3] = 255;
    }
  }
}

經過對imageData的處理,咱們能夠控制每一個像素點,而後你想處理出不一樣的效果,只須要改寫weird方法就能夠了。我寫了5種濾鏡效果,效果以下gif圖:
交互效果gif圖

完整的項目地址在這裏>>>

相關文章
相關標籤/搜索