昨天學習到使用 OffscreenCanvas
在 Web worker
中操作圖像,避免主線程處理 canvas
的耗時操作導致畫面卡頓,今天打算繼續優化前兩天寫的程式,改用 OffscreenCanvas
實作來看是否能提升效能
目的
worker
中使用 OffscreenCanvas
,希望藉此來進一步增進效能說明
worker
處理已經不會讓主線程的動畫卡頓,但當圖片畫質過高,取得圖片像素(getImageData) 所耗費的時間有可能達到 100~200ms,此時還是一樣會造成動畫卡頓OffscreenCanvas
,將 取得圖片像素(getImageData) 的邏輯也移到 worker
中處理。希望最後驗證不論圖片的畫質多高,只要將全部操作圖像的邏輯移到 worker
中執行,都不會阻塞主線程OffscreenCanvas
於 worker
線程中處理首先新增了一個 使用 worker (OffscreenCanvas) 按鈕,按下去後會產生 OffscreenCanvas
,並將 OffscreenCanvas
傳遞到 worker
// 主線程
const offscreenCanvasWorker = new Worker("offscreen-canvas-worker.js");
const canvas = document.createElement("canvas");
const offscreen = canvas.transferControlToOffscreen();
// 將 OffscreenCanvas 傳遞到 worker
offscreenCanvasWorker.postMessage(
{ canvas: offscreen, imageUrl },
[offscreen]
);
由於 worker
中無法控制 DOM
元素,因此原本使用的 new Image()
加載圖片的方式變得不可行,所以改用 fetch
的方式將圖片從網路拿回來,並轉成之後需要用到的 blob
格式
// worker 線程
const fetchImageBlob = async (imageUrl) => {
const response = await fetch(imageUrl);
const imageBlob = await response.blob();
return imageBlob;
};
接著將圖片從 blob
轉為 ImageBitmap
型別,以供下一步的 drawImage 使用
// worker 線程
const bitmap = await createImageBitmap(imageBlob);
將以上準備好的 ImageBitmap
繪製到主線程傳來的 canvas
上
// worker 線程
// canvas 為從主線程傳來的 OffscreenCanvas
const ctx = canvas.getContext("2d");
const width = bitmap.width;
const height = bitmap.height;
canvas.width = width;
canvas.height = height;
ctx.drawImage(bitmap, 0, 0, width, height);
接下來的步驟就跟原先的一樣,先取得 ImageData
後再執行圖像外框透明化的像素操作,最後利用 putImageData
將 ImageData
更新回 OffscreenCanvas
// worker 線程
const imageData = ctx.getImageData(0, 0, width, height);
const newImageData = makeImageDataTransparent(imageData, option);
ctx.putImageData(newImageData, 0, 0);
到目前為止,worker
線程中的 canvas
都處理完操作圖像的邏輯了,由於 worker
中的 canvas
是從主線程傳來的,所以最後只需要將主線程一開始傳入的 canvas
渲染在畫面上
// 主線程
const offscreenCanvasWorker = new Worker("offscreen-canvas-worker.js");
const canvas = document.createElement("canvas");
const offscreen = canvas.transferControlToOffscreen();
offscreenCanvasWorker.postMessage(
{ canvas: offscreen, imageUrl },
[offscreen]
);
offscreenCanvasWorker.onmessage = () => {
// 將 worker 處理過的 canvas 渲染在畫面上
renderCanvas(canvas);
};
OffscreenCanvas
來改善效能,但似乎沒有想像中的順利,可以發現按下按鈕後,紅色方塊動畫一樣有卡頓的現象,看來瓶頸一樣是當執行 取得圖片像素(getImageData) 時會佔用到主線程的資源,推測原因可能是 canvas
一開始就是由主線程傳到 worker
線程,而在 worker
線程呼叫 getImageData
時,主線程一樣需要資源把任何操作圖像的邏輯回寫到主線程的 canvas
上worker
中處理,還是能夠增進效能,避免主線程的動畫卡頓今天的範例中用到了 createImageBitmap
,將 blob
轉換為 ImageBitmap
型態的資料,但 Safari 似乎還不支援以 blob 參數傳入的方式,我實測在 Safari
中執行圖片會跑不出來,但也沒有錯誤訊息出現
// Safari 似乎無法正常處理傳入 blob 格式的資料
const bitmap = await createImageBitmap(imageBlob);
Loading Images with Web Workers