canvas的重頭戲--圖片的處理

前文

相信接觸過一些canvas的小伙們都應該會有這樣的一句感嘆: canvas 強 真的強!
不僅可以靜態的創建一些我們用普通標簽無法實現的圖形,而且還能讓這些圖形動起來.

其實在實際的開發中,要是你只會用canvas畫一些矩形啊,三角形啊,五角星等等的東西肯定是不夠的.
因為真正在開發中,canvas大部分都是用來對圖片以及視頻做處理,所以博主今天在這里想要介紹的是一些關于canvas對圖片的處理

1. 引用圖片

我們知道想在網頁中顯示一張圖片,我們只需要用<img src="">就可以實現了,那么在canvas中我們是怎樣插入一張圖片的呢.

1.首先在body中創建好一個canvas標簽

<body>
  <canvas id="canvas" width="500" height="500"></canvas>
</body>

2.在js代碼中獲取canvas并創建一個<img>元素

<script>
        let canvas = document.querySelector('#canvas')  //獲取canvas對象
        let ctx = canvas.getContext('2d')             //獲取2d上下文
        let img = new Image()                            //創建img
        img.src = 'img/green.jpg'                        //給img添加資源
</script>

3.? 繪制img,考慮到圖片是從網絡加載,如果 drawImage 的時候圖片還沒有完全加載完成,則什么都不做,個別瀏覽器會拋異常。所以我們應該保證在 img 繪制完成之后再 drawImage

<script>
        let canvas = document.querySelector('#canvas')
        let ctx = canvas.getContext('2d')
        let img = new Image()
        img.src = 'img/green.jpg'
        //圖片是否已經加載完成
        img.onload = function () {
            ctx.drawImage(this, 100, 100, this.width / 2, this.height / 2)
        }
    </script>

通過上面的三個步驟,這時候打開你們的瀏覽器就可以在頁面中看到對應的圖片了.
這里主要用到的是drawImage這個方法,下面是對其的一些詳細講解.

2.解析drawImage( )

對于drawImage()這個方法,有三種使用的方式:

第一種:

只傳入3個參數

drawImage(image,x,y)

參數1: image:
指的就是你的圖片對象,
也就是你let img = new Image()中的img
參數2 : x
圖片相對于畫布原點(0,0)也就是畫布的最左上角 的x軸方向的坐標
參數3: y
圖片相對于畫布原點(0,0)也就是畫布的最左上角 的y軸方向的坐標

第二種:

傳入5個參數

drawImage(image,x,y,width,height)

前面三個參數和第一種的使用方式一樣.

參數4,5: width 和 height
可以規定圖片的寬度和高度.
如:在畫布(100,100)的位置插入一張300*300的圖片

        img.onload = function () {
            ctx.drawImage(this, 100, 100, 300, 300)
        }

那么利用width和height我們可以發現,想要將圖片縮減為其原始大小的一半,就可以這樣寫:

        img.onload = function () {
            ctx.drawImage(this,100,100,this.width / 2, this.height / 2)
        }

第三種:

傳入9個參數
當在drawImage()中傳入9個參數后,這個方法的用法將和前面倆種不一樣了.

它的用法是從圖片中截取一定尺寸的圖片,并
drawImage(image,sourceX,sourceY,sourceWidth,sourceHeight,x,y,width,height)

第三種的使用方式傳遞的是9個參數,

參數1 : image
還是圖片的對象

參數2,3 : 從一張大圖上指定要截取小圖的位置(x,y)坐標
參數4,5: 從一張大圖上指定要截取小圖的大小
參數6,7: 從一張大圖上截取下來的小圖要放在canvas(畫布)中的位置(x,y)
參數8,9: 截取下來小圖規定的寬高

如下圖中,有5架小飛機,我只想截取最后一架并顯示在畫布中.

![herofly.png


9G0A~6SHJ`)CJ8AUM8__[LO.png](http://upload-images.jianshu.io/upload_images/7190596-aa463578d541b493.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

整張圖的寬度是330px,一架飛機就是66px,所以最后一張圖就是從66 * 3 = 198px的位置開始截取,截取完后放在畫布(0, 0)的位置

var img1 = new Image()
img1.src = 'img/herofly.png'
drawImage(img1, 198, 0, 66,  82, 0, 0, 66, 82)

3.canvas中的動畫

3.1 requestAnimationFrame的簡介

我們利用普通的定時器來實現動畫的寫法為:

var x = 0;

function animate(){
  //清除畫布內容
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  x += 2;
  ctx.fillStyle = "red";
  ctx.fillRect(x, 0, 50, 50);
  if (x > 200){
    return;
  }
  setTimeout(animate,30);
}
animate();

可以看到上面的動畫是靠setTimeout這個定時器每隔30毫秒調用一次animate() 來實現的.

這種利用定時器來實現動畫效果在移動端實際來說是很不可取的,在移動端上看到的動畫會很卡頓,造成用戶體驗很不流程.
所以ES6新增了一個類似于定時器的API:
requestAnimationFrame()
它只有一個參數,就是要執行的函數.

使用requestAnimationFrame實現動畫

var x = 0;

function animate(){
  //清除畫布內容
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  x += 2;
  ctx.fillStyle = "red";
  ctx.fillRect(x, 0, 50, 50);
  if (x > 200){
    return;
  }
  requestAnimationFrame(animate);           //唯一不同
}
animate();

可以看到倆段代碼的區別,僅僅是一個用的是setTimout,一個是requestAnimationFrame

setTimout表示的是: 每隔30毫秒,執行一次animate()函數.
而requestAnimationFrame 在一秒中執行多少次是由它的應用場景決定的,一般都能達到58~60次.也就是1000/60(相當于定時器16毫秒執行一次)

那么這里得到的1000/60就是一幀.不同的場景幀數可能會不一樣.

3.2 canvas中切換圖片的動畫

還是利用上面的那種飛機圖.我現在想實現一個從第一架完整飛機變化到最后一架爆炸飛機的效果.


herofly.png

那么有心的小伙就會發現了,在js中我們想實現圖片的切換,只要改變背景圖的background-position就可以了,那么在canvas中利用的就是requestAnimationFrame配合drawImage了.

只要不停的改變截取圖片的位置就可以了.
我們來看下面的demo1:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <title>爆炸飛機切換</title>
</head>
<body>
<canvas id="canvas"></canvas>
<script>
    let windowW = document.body.clientWidth
    let windowH = document.body.clientHeight
    let canvas = document.querySelector('#canvas')
    canvas.width = windowW
    canvas.height = windowH
    let ctx = canvas.getContext('2d')
    let frame = 0       //幀數
    let img1 = new Image()
    img1.src = 'img/herofly.png'

    //定義變量:圖片截取的位置(x,y) 圖片截取的寬高(w,h) 整張大圖的寬度, 截取的飛機在canvas中的位置(iX, iY)
    let x = 0, y = 0, w = 66, h = 82, img1W = 330, iX = 0, iY = 0;

    animate()
    function animate() {
        //定義一個幀數的變量,函數每一幀執行一次,則frame就加一次,以此記錄幀數
        frame++
        ctx.clearRect(0, 0, canvas.width, canvas.height)

        //每過20幀執行一次 x += w 以此達到切換圖片的效果
        if(frame % 20 === 0 ) {
            x += w
            if (x >= img1W - w) {   //判定當走到最后一張爆炸圖的時候,讓x又等于0, 達到無限動畫的效果
                x = 0
            }
        }
        //每隔一幀就執行繪畫飛機的操作
        ctx.drawImage(img1, x, y, w, h, iX, iY, w, h)

        //為避免frame加到太大,在這里做一個當frame加到10000時,又讓它為0的操作
        if(frame > 10000 ) {
            frame = 0
        }

        //利用requestAnimationFrame達到動畫效果
        requestAnimationFrame(animate)
    }
</script>
</body>
</html>

3.3 canvas中圖片運動的動畫

上面我們介紹的是在一張大圖中,持續改變它截取圖片的位置(也就是x, y ),來達到切換圖片的效果.這種轉化有些類似于"靜態的轉化".

那么怎樣讓圖片在canvas中移動呢,改變的就是我們drawImage()中的第6,7個參數(也就是截取下來的圖片在canvas中的位置)

還是利用demo1中的那張飛機圖,只不過這次我不讓它"爆炸"了(不進行圖片切換),而是讓它從canvas的最下邊飛到最上邊

來看demo2:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <title>爆炸飛機切換</title>
</head>
<body>
<canvas id="canvas"></canvas>
<script>
    let windowW = document.body.clientWidth
    let windowH = document.body.clientHeight
    let canvas = document.querySelector('#canvas')
    canvas.width = windowW
    canvas.height = windowH
    let ctx = canvas.getContext('2d')
    let frame = 0       //幀數
    let img1 = new Image()
    img1.src = 'img/herofly.png'

    //定義變量:圖片截取的位置(x,y) 圖片截取的寬高(w,h) 整張大圖的寬度, 截取的飛機在canvas中的位置(iX, iY)
    let x = 0, y = 0, w = 66, h = 82, img1W = 330, iX = 0, iY = canvas.height - h;

    animate()
    function animate() {
        //定義一個幀數的變量,函數每一幀執行一次,則frame就加一次,以此記錄幀數
        frame++
        ctx.clearRect(0, 0, canvas.width, canvas.height)

        //每過20幀執行一次 iY -= 4 以此達到圖片運動的效果
        if(frame % 20 === 0 ) {
            iY -= 4
            if (iY <= 0) {   //判定當飛機運動到最上邊的時候,讓iY又等于畫布的高 - 飛機的高, 達到無限動畫的效果
                iY = canvas.height - h
            }
        }
        //每隔一幀就執行繪畫飛機的操作
        ctx.drawImage(img1, x, y, w, h, iX, iY, w, h)

        //為避免frame加到太大,在這里做一個當frame加到10000時,又讓它為0的操作
        if(frame > 10000 ) {
            frame = 0
        }

        //利用requestAnimationFrame達到動畫效果
        requestAnimationFrame(animate)
    }
</script>
</body>
</html>

可以看到上面的demo2 和 demo1 大致相同,只不過此時改變的是iY而已.

3.4 canvas中的視頻

在頁面中,插入一段視頻,只需要使用<video src="video1.mp4"></video>標簽

而在canvas中我們只需要將視頻當圖片一樣插入,在利用canvas中的動畫讓它達到播放的效果.

例1:

<body>
<div class="out">
    <video id="video1" src="img/xiaoyin.mp4" style="width:300px;" autoplay></video>
    <canvas id="myCanvas" width="1000" height="300"></canvas>
</div>

<script>
    let canvas = document.querySelector("#myCanvas")
    let ctx = canvas.getContext("2d")
    let imgObj = document.querySelector('#video1')

    function play(){
        ctx.drawImage(imgObj, 0, 0,canvas.width,canvas.height)
        window.requestAnimationFrame(play);
    }
    play()
</script>
</body>

此時頁面中出現的應該是倆個視頻,并且用canvas繪制出來的視頻并不會卡頓,效果和直接用video的一樣,要是你想只顯示canvas的視頻的話,可以將video1給display:none掉.
效果圖:

image.png

3.5 灰色視頻

在介紹講解灰色視頻之前,我想先介紹一個很牛x的方法getImageData(),這個方法能獲取整張圖片,或者一片圖片區域的所有信息.
用法為:

        ctx.drawImage(imgObj, 0, 0,canvas.width,canvas.height)
        var imageData = ctx.getImageData(0,0, canvas.width, canvas.height);

來看下面這個小例子,點擊按鈕生成,將左側彩色的圖片變為灰色:

image.png

點擊生成:

image.png
<body>
<div class="out">
    <canvas id="canvas" width="300" height="400"></canvas>
    <img id="PutImg" src="" alt="">
    <input id="btn" type="button" value="生成">
</div>
<script>
    let out = document.querySelector('.out')
    let btn = document.querySelector('#btn')
    let PutImg = document.querySelector('#PutImg')
    let canvas = document.querySelector('#canvas')
    let ctx = canvas.getContext('2d')

    let img = new Image()
    img.src = 'img/01.jpg'

    img.onload=function () {
        ctx.drawImage(img,0,0,canvas.width,canvas.height)

        btn.onclick = function () {

            var imageData = ctx.getImageData(0,0, canvas.width, canvas.height);
            console.log(imageData);
            var pixels = imageData.data;

            //遍歷像素點
            for (var i=0; i<pixels.length; i+=4){

                var r = pixels[i];
                var g = pixels[i+1];
                var b = pixels[i+2];

                //獲取灰色
                var gray = parseInt((r+g+b)/3);

                pixels[i] = gray;
                pixels[i+1] = gray;
                pixels[i+2] = gray;
            }
            ctx.putImageData(imageData, 0,0);
            let url = canvas.toDataURL()
            PutImg.src = url

            ctx.clearRect(0,0,canvas.width,canvas.height)
        }

    }
</script>
</body>

我們可以將上面獲取到的imageData對象打印出來看下:

image.png

這個imageData對象中有3個屬性,分別是data,高度,寬度
那么這個data可以看出是一個數組,而且是一個長度為480000的數組
那么這個數組是怎么來的呢.
其實這個數組存儲的是所有像素點的顏色信息
你可以理解為,我的這張圖片是300x400像素的,也就是有120000個像素點,而一個像素點的顏色(也就是rgba) 是由個值組成的,分別是r,g,b,a的值
也就是說數組中每4個值代表的就是一個像素點的信息.
如前4個值[134,134,134,225] 表示的就是第一個像素點(最左上角的)的信息.
所以在做灰色處理時,我們只需要將每個像素點的前三個值全部一樣的就可以了,然后在利用putImageData()方法來輸出一下處理好的圖片.

而視頻的處理也是一樣的
在例1的基礎上加以改進:

<body>
<div class="out">
    <video id="video1" src="img/xiaoyin.mp4" style="width:300px;" autoplay></video>
    <canvas id="myCanvas" width="1000" height="300"></canvas>
</div>

<script>
    let canvas = document.querySelector("#myCanvas")
    let ctx = canvas.getContext("2d")
    let imgObj = document.querySelector('#video1')

    function play(){
        ctx.drawImage(imgObj, 0, 0,canvas.width,canvas.height)

        var imageData = ctx.getImageData(0,0, canvas.width, canvas.height);
       
        var pixels = imageData.data;

        //遍歷像素點
        for (var i=0; i<pixels.length; i+=4){

            var r = pixels[i];
            var g = pixels[i+1];
            var b = pixels[i+2];

            //獲取灰色
            var gray = parseInt((r+g+b)/3);

            pixels[i] = gray;
            pixels[i+1] = gray;
            pixels[i+2] = gray;
        }

        ctx.putImageData(imageData, 0,0);

        window.requestAnimationFrame(play);
    }
    play()
</script>
</body>

此時我們的視頻就變成灰色的了
效果圖:

image.png
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容