[crop系列] 2 canvas drawimage應用

9 mins.
  1. 1. 不是你以為的樣子
  2. 2. 匯出圖片
  3. 3. 結論
  4. 4. 參考

這是接續前一篇[crop系列] 1 scale和position的糾葛的系列之二,前面有提到說要做圖片剪裁,經過了drag和scale後,只要留下一個區塊是我們要的,這時候canvas就出現啦

首先我們會把圖片先放在img的element中,然後再讓使用者drag和scale後,只裁切圓形所顯示的部份,但我們不會真的裁切成圓形,而是方形作為儲存

不是你以為的樣子

首先我們有一個img的elment,實際大小是300*227,指定寬度為200,下面是實際呈現出來的樣式

1
<img src="https://mdn.mozillademos.org/files/5397/rhino.jpg" width="200" id="image" />

接著我們要開始使用canvas,把圖片放進去,直接先指定畫布大小是200*200

1
2
3
4
5
6
const img = document.querySelector('#image');
const canvas = document.createElement('canvas');
canvas.width = 200;
canvas.height = 200;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);

你可以發現到一件事情,圖被切割了!!畫出來的大小竟然不是跟image的元件所產生的是一樣大,而是根據原始檔案

# 指定區域截取

接下來,就是要開始做截取圖片,在drawImage有提供override可以使用,要使用的是其中最多參數的那個,那要先知道每個參數的意義,可以參考一下這篇的說明

放一個50*50的紅框,在畫面上,作為識別我們預期要截取的地方

1
2
3
4
5
6
7
8
.block{
width: 50px;
height: 50px;
border: 1px solid red;
position: absolute;
top: 10px;
left: 100px;
}

在draw的地方改一下參數

1
ctx.drawImage(img, 10, 100, 50, 50, 0, 0, 50, 50);
完蛋了!取到的地方跟我們預期完全不一樣,其實這跟上面所提到的canvas是用實際大小有關係,所以這時候就要來做一點運算,讓位置是符合我們的預期

原圖是300*227,實際是200*151,因此位置要跟著等比例放大,因此先將比例算出來,再拿來計算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const {
offsetWidth,
naturalWidth,
} = img;
const ratio = naturalWidth / offsetWidth;
const {
offsetTop,
offsetLeft,
} = document.querySelector('.block');
const realTop = offsetTop * ratio;
const realLeft = oofsetLeft * ratio;
const realWidth = blockWidth * ratio;
const realHeight = blockHeight * ratio;
ctx.drawImage(img, realLeft, realTop, realWidth, realHeight, 0, 0, 50, 50);
耶,終於成功啦!拿到我們要的區域,那我們就可以匯出成圖片囉

匯出圖片

我們要把截取出來的區域變成blob,最簡單的方法可以使用toBlob,但是要注意在safari是不支援的,那這邊有兩個方法,一個是用polyfill,那另一個就是用toDataURL的作法

這邊介紹的我用的方法toDataURL轉成blob以後丟給api給後端處理

1
2
3
4
5
6
7
8
9
10
11
12
13
// 先輸出成為base64的字串
const image = canvas.toDataURL();
// 將base64的資料還原成array
const byteString = atob(image.split(',')[1]);
// 轉換成byte
const ab = new ArrayBuffer(byteString.length);
const ia = new Uint8Array(ab);
for (let i = 0; i < byteString.length; i += 1) {
ia[i] = byteString.charCodeAt(i);
}
const newBlob = new Blob([ib], {
type: 'image/jpeg',
});

結論

一開始弄的時候被size問題困擾好久,因為還加上了scale的功能,然後怎麼拿到的圖片都不對,後來才發現原來又要拿數學來做計算,然後後來又被一個toBlob搞到,所以才又生出一段轉換的動作

參考