iT邦幫忙

2023 iThome 鐵人賽

DAY 17
0

昨天學習到使用 OffscreenCanvas 在 Web worker 中操作圖像,避免主線程處理 canvas 的耗時操作導致畫面卡頓,今天打算繼續優化前兩天寫的程式,改用 OffscreenCanvas 實作來看是否能提升效能

範例 Demo

目的

  1. 接續 第 15 天的範例,但圖像操作的部分,改成在 worker 中使用 OffscreenCanvas,希望藉此來進一步增進效能

說明

  1. 原先第 15 天的範例中,將 操作圖片像素(ImageData) 的運算改用 worker 處理已經不會讓主線程的動畫卡頓,但當圖片畫質過高,取得圖片像素(getImageData) 所耗費的時間有可能達到 100~200ms,此時還是一樣會造成動畫卡頓
  2. 今天打算使用 OffscreenCanvas,將 取得圖片像素(getImageData) 的邏輯也移到 worker 中處理。希望最後驗證不論圖片的畫質多高,只要將全部操作圖像的邏輯移到 worker 中執行,都不會阻塞主線程
  3. 今天的範例新增了 使用 worker (OffscreenCanvas) 按鈕,按下後操作圖像的邏輯都會使用 OffscreenCanvas 於 worker 線程中處理

https://ithelp.ithome.com.tw/upload/images/20231001/20162687lT25IRrL9i.png

首先新增了一個 使用 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]
);

從網路 fetch Image 圖片

由於 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

接著將圖片從 blob 轉為 ImageBitmap 型別,以供下一步的 drawImage 使用

// worker 線程
const bitmap = await createImageBitmap(imageBlob);

將 ImageBitmap 繪製到 canvas

將以上準備好的 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);

利用 OffscreenCanvas 操作圖像

接下來的步驟就跟原先的一樣,先取得 ImageData 後再執行圖像外框透明化的像素操作,最後利用 putImageData 將 ImageData 更新回 OffscreenCanvas

// worker 線程
const imageData = ctx.getImageData(0, 0, width, height);

const newImageData = makeImageDataTransparent(imageData, option);
ctx.putImageData(newImageData, 0, 0);

主線程中將 canvas 渲染到畫面上

到目前為止,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);
};

結果

https://ithelp.ithome.com.tw/upload/images/20231001/20162687Y5szALCVTE.png

  1. 原本預期使用 OffscreenCanvas 來改善效能,但似乎沒有想像中的順利,可以發現按下按鈕後,紅色方塊動畫一樣有卡頓的現象,看來瓶頸一樣是當執行 取得圖片像素(getImageData) 時會佔用到主線程的資源,推測原因可能是 canvas 一開始就是由主線程傳到 worker 線程,而在 worker 線程呼叫 getImageData 時,主線程一樣需要資源把任何操作圖像的邏輯回寫到主線程的 canvas 上
  2. 但無論如何將 操作像素(ImageData) 使圖片外圍透明化 的邏輯移到 worker 中處理,還是能夠增進效能,避免主線程的動畫卡頓

額外補充

今天的範例中用到了 createImageBitmap,將 blob 轉換為 ImageBitmap 型態的資料,但 Safari 似乎還不支援以 blob 參數傳入的方式,我實測在 Safari 中執行圖片會跑不出來,但也沒有錯誤訊息出現

// Safari 似乎無法正常處理傳入 blob 格式的資料
const bitmap = await createImageBitmap(imageBlob);

Reference

Loading Images with Web Workers


上一篇
Transferable objects - OffscreenCanvas
下一篇
共享的 Web worker - Shared worker
系列文
網頁的另一個大腦:從基礎到進階掌握 Web Worker 技術 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言